diff --git a/.clang-tidy b/.clang-tidy index 92500ad04d..d630063315 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,15 +1,18 @@ Checks: > -*, + boost-*, portability-*, clang-analyzer-*, - -clang-analyzer-optin.*, + -clang-analyzer-optin*, -clang-analyzer-cplusplus.NewDeleteLeaks, - -clang-analyzer-cplusplus.NewDelete, -clang-analyzer-core.CallAndMessage, - modernize-avoid-bind, - readability-identifier-naming -WarningsAsErrors: '*' -HeaderFilterRegex: '(apps|components)/' -CheckOptions: -- key: readability-identifier-naming.ConceptCase - value: CamelCase + -modernize-avoid-bind +WarningsAsErrors: > + -*, + boost-*, + portability-*, + clang-analyzer-*, + -clang-analyzer-optin*, + -clang-analyzer-cplusplus.NewDeleteLeaks, + -clang-analyzer-core.CallAndMessage +HeaderFilterRegex: '^(apps|components)' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d2a7d37990..82386e5292 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -202,11 +202,7 @@ jobs: AWS_DEFAULT_REGION: eu-west-3 if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }} working-directory: ${{ github.workspace }}/SymStore - run: | - choco uninstall awscli -y - choco install awscli -y --version=2.22.35 - aws --version - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym + run: aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym - name: Add install directory to PATH shell: bash diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b6bb7d92e..f1da3eb43c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -228,7 +228,7 @@ Ubuntu_GCC_tests: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml .Ubuntu_GCC_tests_Debug: extends: Ubuntu_GCC @@ -244,7 +244,7 @@ Ubuntu_GCC_tests: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml Ubuntu_GCC_tests_asan: extends: Ubuntu_GCC @@ -262,7 +262,7 @@ Ubuntu_GCC_tests_asan: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml Ubuntu_GCC_tests_ubsan: extends: Ubuntu_GCC @@ -279,7 +279,7 @@ Ubuntu_GCC_tests_ubsan: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml .Ubuntu_GCC_tests_tsan: extends: Ubuntu_GCC @@ -297,7 +297,7 @@ Ubuntu_GCC_tests_ubsan: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml Ubuntu_GCC_tests_coverage: extends: .Ubuntu_GCC_tests_Debug @@ -316,7 +316,7 @@ Ubuntu_GCC_tests_coverage: coverage_report: coverage_format: cobertura path: coverage.xml - junit: build/*-tests.xml + junit: build/*_tests.xml .Ubuntu_Static_Deps: extends: Ubuntu_Clang @@ -357,7 +357,7 @@ Ubuntu_GCC_tests_coverage: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml Ubuntu_Clang: extends: .Ubuntu @@ -440,7 +440,7 @@ Ubuntu_Clang_Tidy_other: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml Ubuntu_Clang_tests_Debug: extends: Ubuntu_Clang @@ -455,7 +455,7 @@ Ubuntu_Clang_tests_Debug: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - junit: build/*-tests.xml + junit: build/*_tests.xml .Ubuntu_integration_tests_base: extends: .Ubuntu_Image @@ -512,16 +512,9 @@ Ubuntu_GCC_integration_tests_asan: - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done - | if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then - echo "[default]" > ~/.s3cfg - echo "access_key = ${AWS_ACCESS_KEY_ID}" >> ~/.s3cfg - echo "secret_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.s3cfg - echo "host_base = rgw.ctrl-c.liu.se" >> ~/.s3cfg - echo "host_bucket = %(bucket)s.rgw.ctrl-c.liu.se" >> ~/.s3cfg - echo "use_https = True" >> ~/.s3cfg - artifactDirectory="${CI_PROJECT_NAMESPACE//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_REF_NAME//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_SHORT_SHA//[\"<>|$'\t'\/\\?*]/_}-${CI_JOB_ID//[\"<>|$'\t'\/\\?*]/_}/" for dmg in *.dmg; do - s3cmd put "${dmg}" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "${dmg}" s3://openmw-artifacts/${artifactDirectory} done fi - ccache -s @@ -547,7 +540,7 @@ macOS14_Xcode15_arm64: script: - apt-get update - apt-get install -y curl gcab unzip - - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.35.zip" -o awscli-exe-linux-x86_64.zip + - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscli-exe-linux-x86_64.zip - unzip -d awscli-exe-linux-x86_64 awscli-exe-linux-x86_64.zip - pushd awscli-exe-linux-x86_64 - ./aws/install @@ -594,7 +587,7 @@ macOS14_Xcode15_arm64: - choco install vswhere -y - choco install ninja -y - choco install python -y - - choco install awscli -y --version=2.22.35 + - choco install awscli -y - refreshenv - | function Make-SafeFileName { @@ -628,20 +621,15 @@ macOS14_Xcode15_arm64: - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - - aws --version - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt - if(!$?) { Exit $LASTEXITCODE } if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} - if(!$?) { Exit $LASTEXITCODE } } Push-Location .. ..\CI\Store-Symbols.ps1 -SkipCompress - if(!$?) { Exit $LASTEXITCODE } 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt - if(!$?) { Exit $LASTEXITCODE } Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } @@ -649,20 +637,13 @@ macOS14_Xcode15_arm64: - | if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} - if(!$?) { Exit $LASTEXITCODE } - } - - | - if ($executables) { - foreach ($exe in $executables.Split(',')) { - & .\$exe - if(!$?) { Exit $LASTEXITCODE } - } } + - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-2022-v12 + key: ninja-2022-v11 paths: - ccache - deps @@ -757,7 +738,7 @@ macOS14_Xcode15_arm64: - choco install 7zip -y - choco install vswhere -y - choco install python -y - - choco install awscli -y --version=2.22.35 + - choco install awscli -y - refreshenv - | function Make-SafeFileName { @@ -786,20 +767,15 @@ macOS14_Xcode15_arm64: - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - - aws --version - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt - if(!$?) { Exit $LASTEXITCODE } if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} - if(!$?) { Exit $LASTEXITCODE } } Push-Location .. ..\CI\Store-Symbols.ps1 -SkipCompress - if(!$?) { Exit $LASTEXITCODE } 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt - if(!$?) { Exit $LASTEXITCODE } Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } @@ -807,20 +783,13 @@ macOS14_Xcode15_arm64: - | if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} - if(!$?) { Exit $LASTEXITCODE } - } - - | - if ($executables) { - foreach ($exe in $executables.Split(',')) { - & .\$exe - if(!$?) { Exit $LASTEXITCODE } - } } + - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-2022-v12 + key: msbuild-2022-v11 paths: - deps - MSVC2022_64/deps/Qt diff --git a/CHANGELOG.md b/CHANGELOG.md index 8656f792df..fab893258f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6097: Level Progress Tooltip Sometimes Not Updated Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6190: Unintuitive sun specularity time of day dependence @@ -225,13 +226,6 @@ Bug #8231: AGOP doesn't like NiCollisionSwitch Bug #8237: Non-bipedal creatures should *not* use spellcast equip/unequip animations Bug #8252: Plugin dependencies are not required to be loaded - Bug #8295: Post-processing chain is case-sensitive - Bug #8299: Crash while smoothing landscape - Bug #8364: Crash when clicking scrollbar without handle (divide by zero) - Bug #8378: Korean bitmap fonts are unusable - Bug #8439: Creatures without models can crash the game - Bug #8441: Freeze when using video main menu replacers - Bug #8462: Crashes when resizing the window on macOS Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking @@ -316,8 +310,6 @@ Feature #8109: Expose commitCrime to Lua API Feature #8130: Launcher: Add the ability to open a selected data directory in the file browser Feature #8145: Starter spell flag is not exposed - Feature #8286: Launcher: Preserve semantically identical openmw.cfg - Feature #8287: Launcher: Special handling for comma in openmw.cfg entries is unintuitive and should be removed Task #5859: User openmw-cs.cfg has comment talking about settings.cfg Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem @@ -396,7 +388,6 @@ Bug #6066: Addtopic "return" does not work from within script. No errors thrown Bug #6067: ESP loader fails for certain subrecord orders Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends - Bug #6097: Level Progress Tooltip Sometimes Not Updated 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 #6109: Crash when playing a custom made menu_background file diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index d73399c102..0120c55202 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -7,7 +7,7 @@ export HOMEBREW_AUTOREMOVE=1 brew tap --repair brew update --quiet -brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd +brew install curl xquartz gd fontconfig freetype harfbuzz brotli command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index c6fd306e25..2589c2807e 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -38,7 +38,7 @@ fi if [[ $CI_CLANG_TIDY ]]; then CMAKE_CONF_OPTS+=( - -DCMAKE_CXX_CLANG_TIDY=clang-tidy + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" -DBUILD_COMPONENTS_TESTS=ON -DBUILD_OPENMW_TESTS=ON -DBUILD_OPENCS_TESTS=ON diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 3ba66133ca..d29f16f55f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -95,7 +95,7 @@ declare -rA GROUPED_DEPS=( [libasan6]="libasan6" [android]="binutils build-essential cmake ccache curl unzip git pkg-config" - + [openmw-clang-format]=" clang-format-14 git-core @@ -126,24 +126,10 @@ export APT_CACHE_DIR="${PWD}/apt-cache" export DEBIAN_FRONTEND=noninteractive set -x mkdir -pv "$APT_CACHE_DIR" - -while true; do - apt-get update -yqq && break -done - +apt-get update -yqq apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null - -while true; do - add-apt-repository -y ppa:openmw/openmw && break -done - -while true; do - add-apt-repository -y ppa:openmw/openmw-daily && break -done - -while true; do - add-apt-repository -y ppa:openmw/staging && break -done - +add-apt-repository -y ppa:openmw/openmw +add-apt-repository -y ppa:openmw/openmw-daily +add-apt-repository -y ppa:openmw/staging apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null apt list --installed diff --git a/CI/run_integration_tests.sh b/CI/run_integration_tests.sh index 6cc3bd55bf..e79408926a 100755 --- a/CI/run_integration_tests.sh +++ b/CI/run_integration_tests.sh @@ -9,7 +9,7 @@ git checkout FETCH_HEAD cd .. xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \ - scripts/integration_tests.py --verbose --omw build/install/bin/openmw --workdir integration_tests_output example-suite/ + scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/ ls integration_tests_output/*.osg_stats.log | while read v; do scripts/osg_stats.py --stats '.*' --regexp_match < "${v}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 94b52e0156..bcb27f7649 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 72) +set(OPENMW_LUA_API_REVISION 69) set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_VERSION_COMMITHASH "") @@ -860,6 +860,7 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) set(BU_CHMOD_BUNDLE_ITEMS ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) include(BundleUtilities) + cmake_minimum_required(VERSION 3.1) " COMPONENT Runtime) set(ABSOLUTE_PLUGINS "") diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index eabd757796..26873d9a03 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -179,7 +179,7 @@ namespace generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); std::size_t n = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { const auto& key = keys[n++ % keys.size()]; auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); diff --git a/apps/benchmarks/esm/benchrefid.cpp b/apps/benchmarks/esm/benchrefid.cpp index 3f38177ca7..b12f494ab9 100644 --- a/apps/benchmarks/esm/benchrefid.cpp +++ b/apps/benchmarks/esm/benchrefid.cpp @@ -104,7 +104,7 @@ namespace std::minstd_rand random; std::vector refIds = generateStringRefIds(state.range(0), random); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(refIds[i].serialize()); if (++i >= refIds.size()) @@ -118,7 +118,7 @@ namespace std::vector serializedRefIds = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); }); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i])); if (++i >= serializedRefIds.size()) @@ -131,7 +131,7 @@ namespace std::minstd_rand random; std::vector refIds = generateStringRefIds(state.range(0), random); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(refIds[i].serializeText()); if (++i >= refIds.size()) @@ -145,7 +145,7 @@ namespace std::vector serializedRefIds = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); }); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); if (++i >= serializedRefIds.size()) @@ -158,7 +158,7 @@ namespace std::minstd_rand random; std::vector refIds = generateGeneratedRefIds(random); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(refIds[i].serializeText()); if (++i >= refIds.size()) @@ -172,7 +172,7 @@ namespace std::vector serializedRefIds = generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); if (++i >= serializedRefIds.size()) @@ -185,7 +185,7 @@ namespace std::minstd_rand random; std::vector refIds = generateIndexRefIds(random); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(refIds[i].serializeText()); if (++i >= refIds.size()) @@ -199,7 +199,7 @@ namespace std::vector serializedRefIds = generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); if (++i >= serializedRefIds.size()) @@ -212,7 +212,7 @@ namespace std::minstd_rand random; std::vector refIds = generateESM3ExteriorCellRefIds(random); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(refIds[i].serializeText()); if (++i >= refIds.size()) @@ -226,7 +226,7 @@ namespace std::vector serializedRefIds = generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); std::size_t i = 0; - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); if (++i >= serializedRefIds.size()) diff --git a/apps/benchmarks/settings/access.cpp b/apps/benchmarks/settings/access.cpp index 9e6999dd1f..7660d0d55e 100644 --- a/apps/benchmarks/settings/access.cpp +++ b/apps/benchmarks/settings/access.cpp @@ -9,7 +9,7 @@ namespace { void settingsManager(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog")); } @@ -17,7 +17,7 @@ namespace void settingsManager2(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); @@ -26,7 +26,7 @@ namespace void settingsManager3(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); @@ -36,7 +36,7 @@ namespace void localStatic(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { static float v = Settings::Manager::getFloat("sky blending start", "Fog"); benchmark::DoNotOptimize(v); @@ -45,7 +45,7 @@ namespace void localStatic2(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); @@ -56,7 +56,7 @@ namespace void localStatic3(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); @@ -69,7 +69,7 @@ namespace void settingsStorage(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { float v = Settings::fog().mSkyBlendingStart.get(); benchmark::DoNotOptimize(v); @@ -78,7 +78,7 @@ namespace void settingsStorage2(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { bool v1 = Settings::postProcessing().mTransparentPostpass.get(); float v2 = Settings::camera().mNearClip.get(); @@ -89,7 +89,7 @@ namespace void settingsStorage3(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { bool v1 = Settings::postProcessing().mTransparentPostpass.get(); float v2 = Settings::camera().mNearClip.get(); @@ -102,7 +102,7 @@ namespace void settingsStorageGet(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::get("Fog", "sky blending start")); } @@ -110,7 +110,7 @@ namespace void settingsStorageGet2(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::get("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get("Camera", "near clip")); @@ -119,7 +119,7 @@ namespace void settingsStorageGet3(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) + for (auto _ : state) { benchmark::DoNotOptimize(Settings::get("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get("Camera", "near clip")); diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index 39065f1410..b2ad8f16b2 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -18,10 +18,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(bsatool gcov) endif() -if (WIN32) - install(TARGETS bsatool RUNTIME DESTINATION ".") -endif() - if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(bsatool PRIVATE diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 5cb275a53d..190eb3364d 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -53,6 +53,8 @@ namespace bpo::options_description makeOptionsDescription() { + using Fallback::FallbackMap; + bpo::options_description result; auto addOption = result.add_options(); addOption("help", "print help message"); @@ -85,8 +87,7 @@ namespace "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default"); - addOption("fallback", - bpo::value()->default_value(Fallback::FallbackMap(), "")->multitoken()->composing(), + addOption("fallback", bpo::value()->default_value(FallbackMap(), "")->multitoken()->composing(), "fallback values"); Files::ConfigurationManager::addCommonOptions(result); diff --git a/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp b/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp index 3094e1cea6..ea9efc3df2 100644 --- a/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp @@ -5,9 +5,7 @@ #include #include #include -#include #include -#include #include @@ -374,106 +372,6 @@ namespace } } - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_recast_mesh) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteRecastMeshToFile = true; - const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest"); - mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_TRUE(std::filesystem::exists(dir / "0.0.recastmesh.obj")); - } - - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_recast_mesh_with_revision) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteRecastMeshToFile = true; - mSettings.mEnableRecastMeshFileNameRevision = true; - const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest"); - mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_TRUE(std::filesystem::exists(dir / "0.0.recastmesh.1.2.obj")); - } - - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, writing_recast_mesh_to_absent_file_should_not_fail_tile_generation) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteRecastMeshToFile = true; - const std::filesystem::path dir = TestingOpenMW::outputDir() / "absent"; - mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); - EXPECT_FALSE(std::filesystem::exists(dir)); - } - - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_navmesh) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteNavMeshToFile = true; - const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest"); - mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_TRUE(std::filesystem::exists(dir / "all_tiles_navmesh.bin")); - } - - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_navmesh_with_revision) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteNavMeshToFile = true; - mSettings.mEnableNavMeshFileNameRevision = true; - const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest"); - mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_TRUE(std::filesystem::exists(dir / "all_tiles_navmesh.1.1.bin")); - } - - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, writing_navmesh_to_absent_file_should_not_fail_tile_generation) - { - mRecastMeshManager.setWorldspace(mWorldspace, nullptr); - addHeightFieldPlane(mRecastMeshManager); - mSettings.mEnableWriteNavMeshToFile = true; - const std::filesystem::path dir = TestingOpenMW::outputDir() / "absent"; - mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/"; - Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); - const auto navMeshCacheItem = std::make_shared(1, mSettings); - const std::map changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } }; - updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); - updater.wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); - EXPECT_FALSE(std::filesystem::exists(dir)); - } - struct DetourNavigatorSpatialJobQueueTest : Test { const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; diff --git a/apps/components_tests/esm3/readerscache.cpp b/apps/components_tests/esm3/readerscache.cpp index 1cb9a85fb6..f222a29bf8 100644 --- a/apps/components_tests/esm3/readerscache.cpp +++ b/apps/components_tests/esm3/readerscache.cpp @@ -88,31 +88,4 @@ namespace EXPECT_EQ(reader->getFileOffset(), sInitialOffset); } } - - TEST_F(ESM3ReadersCacheWithContentFile, CachedSizeAndName) - { - ESM::ReadersCache readers(2); - { - readers.get(0)->openRaw(std::make_unique("123"), "closed0.omwaddon"); - readers.get(1)->openRaw(std::make_unique("12345"), "closed1.omwaddon"); - readers.get(2)->openRaw(std::make_unique("1234567"), "free.omwaddon"); - } - auto busy = readers.get(3); - busy->openRaw(std::make_unique("123456789"), "busy.omwaddon"); - - EXPECT_EQ(readers.getFileSize(0), 3); - EXPECT_EQ(readers.getName(0), "closed0.omwaddon"); - - EXPECT_EQ(readers.getFileSize(1), 5); - EXPECT_EQ(readers.getName(1), "closed1.omwaddon"); - - EXPECT_EQ(readers.getFileSize(2), 7); - EXPECT_EQ(readers.getName(2), "free.omwaddon"); - - EXPECT_EQ(readers.getFileSize(3), 9); - EXPECT_EQ(readers.getName(3), "busy.omwaddon"); - - // not-yet-seen indices give zero for their size - EXPECT_EQ(readers.getFileSize(4), 0); - } } diff --git a/apps/components_tests/files/hash.cpp b/apps/components_tests/files/hash.cpp index f94cb1969d..793965112b 100644 --- a/apps/components_tests/files/hash.cpp +++ b/apps/components_tests/files/hash.cpp @@ -30,7 +30,7 @@ namespace TEST(FilesGetHash, shouldClearErrors) { - const auto fileName = outputFilePath("fileName"); + const auto fileName = temporaryFilePath("fileName"); std::string content; std::fill_n(std::back_inserter(content), 1, 'a'); std::istringstream stream(content); @@ -41,7 +41,7 @@ namespace TEST_P(FilesGetHash, shouldReturnHashForStringStream) { - const auto fileName = outputFilePath("fileName"); + const auto fileName = temporaryFilePath("fileName"); std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::istringstream stream(content); diff --git a/apps/components_tests/lua/test_inputactions.cpp b/apps/components_tests/lua/test_inputactions.cpp index b2a84f193c..cad17a5b99 100644 --- a/apps/components_tests/lua/test_inputactions.cpp +++ b/apps/components_tests/lua/test_inputactions.cpp @@ -38,10 +38,10 @@ namespace sol::state lua; LuaUtil::InputAction::Registry registry; LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description", - sol::make_object(lua, false), false }); + sol::make_object(lua, false) }); registry.insert(a); LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description", - sol::make_object(lua, false), false }); + sol::make_object(lua, false) }); registry.insert(b); LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) }); LuaUtil::Callback bindBToA( diff --git a/apps/components_tests/main.cpp b/apps/components_tests/main.cpp index c1b41d184a..dcfb2e9ba9 100644 --- a/apps/components_tests/main.cpp +++ b/apps/components_tests/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include @@ -25,9 +24,5 @@ int main(int argc, char** argv) Settings::StaticValues::init(); testing::InitGoogleTest(&argc, argv); - - const int result = RUN_ALL_TESTS(); - if (result == 0) - std::filesystem::remove_all(TestingOpenMW::outputDir()); - return result; + return RUN_ALL_TESTS(); } diff --git a/apps/components_tests/misc/testmathutil.cpp b/apps/components_tests/misc/testmathutil.cpp index 099bd6db9e..c4b545c2f4 100644 --- a/apps/components_tests/misc/testmathutil.cpp +++ b/apps/components_tests/misc/testmathutil.cpp @@ -51,7 +51,7 @@ namespace Misc const std::pair eulerAnglesXZQuat[] = { { osg::Quat(1, 0, 0, 0), - osg::Vec3f(0, 0, osg::PIf), + osg::Vec3f(0, 0, osg::PI), }, { osg::Quat(0, 1, 0, 0), @@ -59,7 +59,7 @@ namespace Misc }, { osg::Quat(0, 0, 1, 0), - osg::Vec3f(0, 0, osg::PIf), + osg::Vec3f(0, 0, osg::PI), }, { osg::Quat(0, 0, 0, 1), @@ -128,15 +128,15 @@ namespace Misc const std::pair eulerAnglesZYXQuat[] = { { osg::Quat(1, 0, 0, 0), - osg::Vec3f(osg::PIf, 0, 0), + osg::Vec3f(osg::PI, 0, 0), }, { osg::Quat(0, 1, 0, 0), - osg::Vec3f(osg::PIf, 0, osg::PIf), + osg::Vec3f(osg::PI, 0, osg::PI), }, { osg::Quat(0, 0, 1, 0), - osg::Vec3f(0, 0, osg::PIf), + osg::Vec3f(0, 0, osg::PI), }, { osg::Quat(0, 0, 0, 1), diff --git a/apps/components_tests/shader/shadermanager.cpp b/apps/components_tests/shader/shadermanager.cpp index b80839d0ec..5b11d31a44 100644 --- a/apps/components_tests/shader/shadermanager.cpp +++ b/apps/components_tests/shader/shadermanager.cpp @@ -16,7 +16,7 @@ namespace ShaderManager mManager; ShaderManager::DefineMap mDefines; - ShaderManagerTest() { mManager.setShaderPath(TestingOpenMW::outputDir()); } + ShaderManagerTest() { mManager.setShaderPath("tests_output"); } template void withShaderFile(const std::string& content, F&& f) diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 963f4f5508..6dd592a4fe 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -25,10 +25,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(esmtool gcov) endif() -if (WIN32) - install(TARGETS esmtool RUNTIME DESTINATION ".") -endif() - if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(esmtool PRIVATE diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index c1bcc8f944..9bb54fdb8e 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -352,17 +351,9 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!resourcesVfs.isEmpty()) directories.insert(0, { resourcesVfs }); - QIcon containsDataIcon(":/images/openmw-plugin.png"); - - QProgressDialog progressBar("Adding data directories", {}, 0, directories.count(), this); - progressBar.setWindowModality(Qt::WindowModal); - progressBar.setValue(0); - std::unordered_set visitedDirectories; for (const Config::SettingValue& currentDir : directories) { - progressBar.setValue(progressBar.value() + 1); - if (!visitedDirectories.insert(currentDir.value).second) continue; @@ -411,7 +402,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // Add a "data file" icon if the directory contains a content file if (mSelector->containsDataFiles(currentDir.value)) { - item->setIcon(containsDataIcon); + item->setIcon(QIcon(":/images/openmw-plugin.png")); tooltip << tr("Contains content file(s)"); } @@ -774,7 +765,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) return; QString rootPath = QFileDialog::getExistingDirectory( - this, tr("Select Directory"), {}, QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); + this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); if (rootPath.isEmpty()) return; diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt index f056c93685..090fc00f36 100644 --- a/apps/navmeshtool/CMakeLists.txt +++ b/apps/navmeshtool/CMakeLists.txt @@ -1,16 +1,20 @@ -set(NAVMESHTOOL_LIB +set(NAVMESHTOOL worldspacedata.cpp navmesh.cpp + main.cpp +) +source_group(apps\\navmeshtool FILES ${NAVMESHTOOL}) + +add_library(openmw-navmeshtool-lib STATIC + ${NAVMESHTOOL} ) -source_group(apps\\navmeshtool FILES ${NAVMESHTOOL_LIB} main.cpp) - -add_library(openmw-navmeshtool-lib STATIC ${NAVMESHTOOL_LIB}) - if (ANDROID) - add_library(openmw-navmeshtool SHARED main.cpp) + add_library(openmw-navmeshtool SHARED + main.cpp + ) else() - openmw_add_executable(openmw-navmeshtool main.cpp) + openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) endif() target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib) diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index e148e60d54..27f84104ac 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -62,6 +62,8 @@ namespace NavMeshTool bpo::options_description makeOptionsDescription() { + using Fallback::FallbackMap; + bpo::options_description result; auto addOption = result.add_options(); addOption("help", "print help message"); @@ -223,8 +225,7 @@ namespace NavMeshTool Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); - DetourNavigator::Settings navigatorSettings - = DetourNavigator::makeSettingsFromSettingsManager(Debug::getRecastMaxLogLevel()); + DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index da2cfac785..cf37162f6e 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -17,10 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(niftest gcov) endif() -if (WIN32) - install(TARGETS niftest RUNTIME DESTINATION ".") -endif() - if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(niftest PRIVATE ) endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 8781f54154..4cab88e5f2 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -38,6 +38,8 @@ #include "view/doc/viewmanager.hpp" +using namespace Fallback; + CS::Editor::Editor(int argc, char** argv) : mConfigVariables(readConfiguration()) , mSettingsState(mCfgMgr) @@ -122,10 +124,7 @@ boost::program_options::variables_map CS::Editor::readConfiguration() ->default_value(std::vector(), "fallback-archive") ->multitoken()); addOption("fallback", - boost::program_options::value() - ->default_value(Fallback::FallbackMap(), "") - ->multitoken() - ->composing(), + boost::program_options::value()->default_value(FallbackMap(), "")->multitoken()->composing(), "fallback values"); Files::ConfigurationManager::addCommonOptions(desc); @@ -142,7 +141,7 @@ std::pair> CS::Editor::readConfig { boost::program_options::variables_map& variables = mConfigVariables; - Fallback::Map::init(variables["fallback"].as().mMap); + Fallback::Map::init(variables["fallback"].as().mMap); mEncodingName = variables["encoding"].as(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index afcab50ead..7ee8092df6 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -1164,30 +1164,23 @@ void CSVDoc::View::onRequestFocus(const std::string& id) QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position) { QScreen* screen = QApplication::screenAt(position); - if (screen) - return screen; - - const QList screens = QApplication::screens(); - if (screens.isEmpty()) - throw std::runtime_error("No screens available"); - - int closestDistance = std::numeric_limits::max(); - for (QScreen* candidate : screens) + if (screen == nullptr) { - const QRect geometry = candidate->geometry(); - const int dx = position.x() - std::clamp(position.x(), geometry.left(), geometry.right()); - const int dy = position.y() - std::clamp(position.y(), geometry.top(), geometry.bottom()); - const int distance = dx * dx + dy * dy; + QPoint clampedPosition = position; - if (distance < closestDistance) - { - closestDistance = distance; - screen = candidate; - } + // If we failed to find the screen, + // clamp negative positions and try again + if (clampedPosition.x() <= 0) + clampedPosition.setX(0); + if (clampedPosition.y() <= 0) + clampedPosition.setY(0); + + screen = QApplication::screenAt(clampedPosition); } if (screen == nullptr) - screen = screens.first(); + throw std::runtime_error( + Misc::StringUtils::format("Can not detect the screen for position [%d, %d]", position.x(), position.y())); return screen; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index fb085f075a..7a59222eff 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -60,6 +60,24 @@ #include "pagedworldspacewidget.hpp" #include "worldspacewidget.hpp" +namespace +{ + constexpr std::string_view sInstanceModeTooltip = R"( + Instance editing +
  • Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances
  • +
  • Use {scene-edit-primary} to manipulate instances
  • +
  • Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap + selection relative to the reference object
  • +
  • Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, rotate, and + scale modes respectively
  • +
  • Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes + respectively
  • +
  • Use {scene-delete} to delete currently selected objects
  • +
  • Use {scene-duplicate} to duplicate instances
  • +
  • Use {scene-instance-drop} to drop instances
+)"; +} + int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const { return id == "move" ? 0 : (id == "rotate" ? 1 : 2); @@ -294,28 +312,10 @@ void CSVRender::InstanceMode::setDragAxis(const char axis) mDragAxis = newDragAxis; } -QString CSVRender::InstanceMode::getTooltip() -{ - return QString( - "Instance editing" - "
  • Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances
  • " - "
  • Use {scene-edit-primary} to manipulate instances
  • " - "
  • Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap " - "selection relative to the reference object
  • " - "
  • Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, " - "rotate, and " - "scale modes respectively
  • " - "
  • Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes " - "respectively
  • " - "
  • Use {scene-delete} to delete currently selected objects
  • " - "
  • Use {scene-duplicate} to duplicate instances
  • " - "
  • Use {scene-instance-drop} to drop instances
"); -} - CSVRender::InstanceMode::InstanceMode( WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"), - Mask_Reference | Mask_Terrain, getTooltip(), parent) + Mask_Reference | Mask_Terrain, sInstanceModeTooltip.data(), parent) , mSubMode(nullptr) , mSubModeId("move") , mSelectionMode(nullptr) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index f4dd9d99ea..193423efd5 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -53,7 +53,6 @@ namespace CSVRender std::vector mObjectsAtDragStart; CSMWorld::IdTable* mSelectionGroups; - QString getTooltip(); int getSubModeFromId(const std::string& id) const; osg::Vec3 quatToEuler(const osg::Quat& quat) const; diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index bc1c6c6365..b9dc301efa 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -761,17 +761,18 @@ void CSVRender::TerrainShapeMode::smoothHeight( // this = this Cell // left = x - 1, up = y - 1, right = x + 1, down = y + 1 // Altered = transient edit (in current edited) + float thisAlteredHeight = 0.0f; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) + thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; - float* thisAlteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); - float thisAlteredHeight = thisAlteredHeightPtr != nullptr ? *thisAlteredHeightPtr : 0.f; - float leftHeight = thisHeight; - float leftAlteredHeight = thisAlteredHeight; - float rightHeight = thisHeight; - float rightAlteredHeight = thisAlteredHeight; - float downHeight = thisHeight; - float downAlteredHeight = thisAlteredHeight; - float upHeight = thisHeight; - float upAlteredHeight = thisAlteredHeight; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + float upHeight = 0.0f; if (allowLandShapeEditing(cellId)) { @@ -779,80 +780,70 @@ void CSVRender::TerrainShapeMode::smoothHeight( if (inCellX == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); - if (isLandLoaded(cellId)) - { - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer - = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) - .value(); - leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; - float* alteredHeightPtr - = paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); - leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; - } + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) + leftAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); } if (inCellY == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); - if (isLandLoaded(cellId)) - { - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer - = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) - .value(); - upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; - float* alteredHeightPtr - = paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); - upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; - } + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) + upAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); } if (inCellX > 0) { leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); - leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); } if (inCellY > 0) { upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); - upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); } if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); - if (isLandLoaded(cellId)) + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer - = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) - .value(); - rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); - rightAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); } } if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - if (isLandLoaded(cellId)) + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer - = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) - .value(); - downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); - downAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); } } if (inCellX < ESM::Land::LAND_SIZE - 1) { rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); - rightAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); } if (inCellY < ESM::Land::LAND_SIZE - 1) { downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; - float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); - downAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); } float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight diff --git a/apps/opencs_tests/main.cpp b/apps/opencs_tests/main.cpp index fed1cd2bb1..fd7d4900c8 100644 --- a/apps/opencs_tests/main.cpp +++ b/apps/opencs_tests/main.cpp @@ -1,5 +1,4 @@ #include -#include #include @@ -8,9 +7,5 @@ int main(int argc, char* argv[]) Log::sMinDebugLevel = Debug::getDebugLevel(); testing::InitGoogleTest(&argc, argv); - - const int result = RUN_ALL_TESTS(); - if (result == 0) - std::filesystem::remove_all(TestingOpenMW::outputDir()); - return result; + return RUN_ALL_TESTS(); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7dc372f269..2736f339e4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -373,15 +373,11 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mScriptConsoleMode(false) , mActivationDistanceOverride(-1) , mGrab(true) - , mExportFonts(false) , mRandomSeed(0) , mNewGame(false) , mCfgMgr(configurationManager) , mGlMaxTextureImageUnits(0) { -#if SDL_VERSION_ATLEAST(2, 24, 0) - SDL_SetHint(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1"); -#endif SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags @@ -811,7 +807,7 @@ void OMW::Engine::prepareEngine() rootNode->addChild(guiRoot); mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), - mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, + mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr); mEnvironment.setWindowManager(*mWindowManager); @@ -851,7 +847,7 @@ void OMW::Engine::prepareEngine() } listener->loadingOff(); - mWorld->init(mMaxRecastLogLevel, mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue); + mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue); mEnvironment.setWorldScene(mWorld->getWorldScene()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); @@ -1113,11 +1109,6 @@ void OMW::Engine::setWarningsMode(int mode) mWarningsMode = mode; } -void OMW::Engine::enableFontExport(bool exportFonts) -{ - mExportFonts = exportFonts; -} - void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame) { mSaveGameFile = savegame; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 38e95ea7c8..39056af560 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -172,9 +171,7 @@ namespace OMW // Grab mouse? bool mGrab; - bool mExportFonts; unsigned int mRandomSeed; - Debug::Level mMaxRecastLogLevel = Debug::Error; Compiler::Extensions mExtensions; std::unique_ptr mScriptContext; @@ -183,9 +180,6 @@ namespace OMW Translation::Storage mTranslationDataStorage; bool mNewGame; - Files::ConfigurationManager& mCfgMgr; - int mGlMaxTextureImageUnits; - // not implemented Engine(const Engine&); Engine& operator=(const Engine&); @@ -257,14 +251,14 @@ namespace OMW void setWarningsMode(int mode); - void enableFontExport(bool exportFonts); - /// Set the save game file to load after initialising the engine. void setSaveGameFile(const std::filesystem::path& savegame); void setRandomSeed(unsigned int seed); - void setRecastMaxLogLevel(Debug::Level value) { mMaxRecastLogLevel = value; } + private: + Files::ConfigurationManager& mCfgMgr; + int mGlMaxTextureImageUnits; }; } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 812a6ffd85..5b89bca960 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -28,6 +28,8 @@ extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x #include #endif +using namespace Fallback; + /** * \brief Parses application command line and calls \ref Cfg::ConfigurationManager * to parse configuration files. @@ -150,10 +152,9 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati engine.setSaveGameFile(variables["load-savegame"].as().u8string()); // other settings - Fallback::Map::init(variables["fallback"].as().mMap); + Fallback::Map::init(variables["fallback"].as().mMap); engine.setSoundUsage(!variables["no-sound"].as()); engine.setActivationDistanceOverride(variables["activate-dist"].as()); - engine.enableFontExport(variables["export-fonts"].as()); engine.setRandomSeed(variables["random-seed"].as()); return true; @@ -219,8 +220,6 @@ int runApplication(int argc, char* argv[]) Files::ConfigurationManager cfgMgr; std::unique_ptr engine = std::make_unique(cfgMgr); - engine->setRecastMaxLogLevel(Debug::getRecastMaxLogLevel()); - if (parseOptions(argc, argv, *engine, cfgMgr)) { if (!Misc::checkRequiredOSGPluginsArePresent()) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 23d79c1a6b..4883fa2000 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -200,7 +200,7 @@ namespace MWBase ///< Skip the animation for the given MW-reference for one frame. Calls to this function for /// references that are currently not in the scene should be ignored. - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) = 0; + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 8164501b4b..df334bbfe8 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -363,7 +363,7 @@ namespace MWBase void windowVisibilityChange(bool visible) override = 0; void windowResized(int x, int y) override = 0; void windowClosed() override = 0; - virtual bool isWindowVisible() const = 0; + virtual bool isWindowVisible() = 0; virtual void watchActor(const MWWorld::Ptr& ptr) = 0; virtual MWWorld::Ptr getWatchedActor() const = 0; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index c8b1f05972..8327904ecd 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -237,12 +237,7 @@ namespace MWClass bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) - { - if (!canBeHarvested(ptr)) - return true; - const MWWorld::ContainerStore& store = data->asContainerCustomData().mStore; - return !store.isResolved() || store.hasVisibleItems(); - } + return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems(); return true; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 9224f6f0d8..007338095f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -236,8 +236,11 @@ namespace MWClass } MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + float dist = store.find("fCombatDistance")->mValue.getFloat(); + if (!weapon.isEmpty()) + dist *= weapon.get()->mBase->mData.mReach; - const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon); const std::pair result = MWMechanics::getHitContact(ptr, dist); if (result.first.isEmpty()) // Didn't hit anything return true; @@ -278,9 +281,6 @@ namespace MWClass if (otherstats.isDead()) // Can't hit dead actors return; - if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon))) - return; - if (!success) { victim.getClass().onHit( diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0b61436d11..22c953d27d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -572,8 +572,12 @@ namespace MWClass weapon = *weaponslot; MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); + float dist = fCombatDistance + * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach + : store.find("fHandToHandReach")->mValue.getFloat()); - const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon); const std::pair result = MWMechanics::getHitContact(ptr, dist); if (result.first.isEmpty()) // Didn't hit anything return true; @@ -611,9 +615,6 @@ namespace MWClass if (otherstats.isDead()) // Can't hit dead actors return; - if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon))) - return; - if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 47e85b1f4b..1966442513 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1343,13 +1343,12 @@ namespace MWGui return codePoint == '\r'; } - // Normal no-break space (0x00A0) is ignored here - // because Morrowind compatibility requires us to render its glyph static bool ucsSpace(int codePoint) { switch (codePoint) { case 0x0020: // SPACE + case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD @@ -1374,14 +1373,12 @@ namespace MWGui } } - // No-break spaces (0x00A0, 0x202F, 0xFEFF - normal, narrow, zero width) - // are ignored here for obvious reasons - // Figure space (0x2007) is not a breaking space either static bool ucsBreakingSpace(int codePoint) { switch (codePoint) { case 0x0020: // SPACE + // case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD @@ -1391,12 +1388,15 @@ namespace MWGui case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x3000: // IDEOGRAPHIC SPACE + // case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE return true; default: return false; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index e14c400978..6f154bb134 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -666,8 +666,7 @@ namespace MWGui else if (scrollbar) { mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); - // Scroll range should be >= 2 to enable scrolling and prevent a crash - size_t range = std::max(book->getSize().second - viewHeight, size_t(2)); + size_t range = book->getSize().second - viewHeight; mScrollBar->setScrollRange(range); mScrollBar->setScrollPosition(range - 1); mScrollBar->setTrackSize( diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1b3619bd9f..da747dd7a2 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -29,26 +29,11 @@ namespace MWGui { Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); - const MWBase::WindowManager& windowManager = *MWBase::Environment::get().getWindowManager(); - bool paused = false; while (mRunning) { - if (windowManager.isWindowVisible()) - { - if (paused) - { - mVideo->resume(); - paused = false; - } - // If finished playing, start again - if (!mVideo->update()) - mVideo->playVideo("video\\menu_background.bik"); - } - else if (!paused) - { - paused = true; - mVideo->pause(); - } + // If finished playing, start again + if (!mVideo->update()) + mVideo->playVideo("video\\menu_background.bik"); frameRateLimiter.limit(); } } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 565fb43127..e51350d19f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -146,7 +146,7 @@ namespace MWGui WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, - bool exportFonts, const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr) + const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr) : mOldUpdateMask(0) , mOldCullMask(0) , mStore(nullptr) @@ -215,8 +215,7 @@ namespace MWGui MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts - mFontLoader - = std::make_unique(encoding, resourceSystem->getVFS(), mScalingFactor, exportFonts); + mFontLoader = std::make_unique(encoding, resourceSystem->getVFS(), mScalingFactor); // Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -730,9 +729,6 @@ namespace MWGui return; } - if (mGuiModes.empty()) - return; - GuiModeState& state = mGuiModeStates[mGuiModes.back()]; for (const auto& window : state.mWindows) { @@ -1218,7 +1214,7 @@ namespace MWGui // TODO: check if any windows are now off-screen and move them back if so } - bool WindowManager::isWindowVisible() const + bool WindowManager::isWindowVisible() { return mWindowVisible; } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 03902e21c4..052a269188 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -128,7 +128,7 @@ namespace MWGui WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, - ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, bool useShaders, + ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr); virtual ~WindowManager(); @@ -290,7 +290,7 @@ namespace MWGui void windowVisibilityChange(bool visible) override; void windowResized(int x, int y) override; void windowClosed() override; - bool isWindowVisible() const override; + bool isWindowVisible() override; void watchActor(const MWWorld::Ptr& ptr) override; MWWorld::Ptr getWatchedActor() const override; diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 9df435c00d..445bcdd617 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -148,7 +148,7 @@ namespace MWLua }; } - sol::table readOnlyApi = LuaUtil::makeReadOnly(api); - return context.setTypePackage(readOnlyApi, "openmw_core"); + sol::table readOnly = LuaUtil::makeReadOnly(api); + return context.setTypePackage(readOnly, "openmw_core"); } } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index c3b47c5061..b9affcdfca 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -38,116 +38,94 @@ namespace MWLua sol::table initInputPackage(const Context& context) { - sol::object cached = context.getTypePackage("openmw_input"); - if (cached != sol::nil) - return cached; sol::state_view lua = context.sol(); + { + if (lua["openmw_input"] != sol::nil) + return lua["openmw_input"]; + } - context.cachePackage("openmw_input_keyevent", [&lua]() { - sol::usertype keyEvent = lua.new_usertype("KeyEvent"); - 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; }); - keyEvent["withSuper"] - = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); - - return sol::table(lua, sol::create); + sol::usertype keyEvent = lua.new_usertype("KeyEvent"); + 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; }); + keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); - context.cachePackage("openmw_input_touchpadevent", [&lua]() { - auto touchpadEvent = lua.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 { e.mX, e.mY }; - }); - touchpadEvent["pressure"] - = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); - return sol::table(lua, sol::create); + auto touchpadEvent = lua.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 { e.mX, e.mY }; }); + touchpadEvent["pressure"] + = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); - context.cachePackage("openmw_input_inputactions", [&lua]() { - auto inputActions = lua.new_usertype("InputActions"); - inputActions[sol::meta_function::index] - = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; - { - auto pairs = [](LuaUtil::InputAction::Registry& registry) { - auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key) - -> sol::optional> { - std::optional nextKey(registry.nextKey(key)); - if (!nextKey.has_value()) - return sol::nullopt; - else - return std::make_tuple(*nextKey, registry[*nextKey].value()); - }; - return std::make_tuple(next, registry, registry.firstKey()); + auto inputActions = lua.new_usertype("InputActions"); + inputActions[sol::meta_function::index] + = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputAction::Registry& registry) { + auto next + = [](LuaUtil::InputAction::Registry& registry, + std::string_view key) -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); }; - inputActions[sol::meta_function::pairs] = pairs; - } - return sol::table(lua, sol::create); - }); + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputActions[sol::meta_function::pairs] = pairs; + } - context.cachePackage("openmw_input_actioninfo", [&lua]() { - auto actionInfo = lua.new_usertype("ActionInfo"); - actionInfo["key"] = sol::readonly_property( - [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); - actionInfo["name"] = sol::readonly_property( - [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; }); - actionInfo["description"] = sol::readonly_property( - [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); - actionInfo["l10n"] = sol::readonly_property( - [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); - actionInfo["type"] - = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; }); - actionInfo["defaultValue"] - = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); - return sol::table(lua, sol::create); - }); + auto actionInfo = lua.new_usertype("ActionInfo"); + actionInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); + actionInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; }); + actionInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); + actionInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); + actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; }); + actionInfo["defaultValue"] + = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); - context.cachePackage("openmw_input_inputtriggers", [&lua]() { - auto inputTriggers = lua.new_usertype("InputTriggers"); - inputTriggers[sol::meta_function::index] - = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; - { - auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { - auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) - -> sol::optional> { - std::optional nextKey(registry.nextKey(key)); - if (!nextKey.has_value()) - return sol::nullopt; - else - return std::make_tuple(*nextKey, registry[*nextKey].value()); - }; - return std::make_tuple(next, registry, registry.firstKey()); + auto inputTriggers = lua.new_usertype("InputTriggers"); + inputTriggers[sol::meta_function::index] + = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { + auto next + = [](LuaUtil::InputTrigger::Registry& registry, + std::string_view key) -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); }; - inputTriggers[sol::meta_function::pairs] = pairs; - } - return sol::table(lua, sol::create); - }); + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputTriggers[sol::meta_function::pairs] = pairs; + } - context.cachePackage("openmw_input_triggerinfo", [&lua]() { - auto triggerInfo = lua.new_usertype("TriggerInfo"); - triggerInfo["key"] = sol::readonly_property( - [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); - triggerInfo["name"] = sol::readonly_property( - [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; }); - triggerInfo["description"] = sol::readonly_property( - [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); - triggerInfo["l10n"] = sol::readonly_property( - [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); - return sol::table(lua, sol::create); - }); + auto triggerInfo = lua.new_usertype("TriggerInfo"); + triggerInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); + triggerInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; }); + triggerInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); + triggerInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(lua, sol::create); @@ -161,18 +139,16 @@ namespace MWLua })); api["actions"] = std::ref(context.mLuaManager->inputActions()); - api["registerAction"] - = [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) { - LuaUtil::InputAction::Info parsedOptions; - parsedOptions.mKey = options["key"].get(); - parsedOptions.mType = options["type"].get(); - parsedOptions.mL10n = options["l10n"].get(); - parsedOptions.mName = options["name"].get(); - parsedOptions.mDescription = options["description"].get(); - parsedOptions.mDefaultValue = options["defaultValue"].get(); - parsedOptions.mPersistent = persistent; - manager->inputActions().insert(std::move(parsedOptions)); - }; + api["registerAction"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputAction::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mType = options["type"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + parsedOptions.mDefaultValue = options["defaultValue"].get(); + manager->inputActions().insert(std::move(parsedOptions)); + }; api["bindAction"] = [manager = context.mLuaManager]( std::string_view key, const sol::table& callback, sol::table dependencies) { std::vector parsedDependencies; @@ -202,16 +178,14 @@ namespace MWLua }; api["triggers"] = std::ref(context.mLuaManager->inputTriggers()); - api["registerTrigger"] - = [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) { - LuaUtil::InputTrigger::Info parsedOptions; - parsedOptions.mKey = options["key"].get(); - parsedOptions.mL10n = options["l10n"].get(); - parsedOptions.mName = options["name"].get(); - parsedOptions.mDescription = options["description"].get(); - parsedOptions.mPersistent = persistent; - manager->inputTriggers().insert(std::move(parsedOptions)); - }; + api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputTrigger::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + manager->inputTriggers().insert(std::move(parsedOptions)); + }; api["registerTriggerHandler"] = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback)); @@ -471,8 +445,8 @@ namespace MWLua { "Tab", SDL_SCANCODE_TAB }, })); - sol::table readOnlyApi = LuaUtil::makeReadOnly(api); - return context.setTypePackage(readOnlyApi, "openmw_input"); + lua["openmw_input"] = LuaUtil::makeReadOnly(api); + return lua["openmw_input"]; } } diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 1b809383db..38415ea25e 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -71,8 +71,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] - = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; } else throw std::runtime_error("Only global or self scripts can set the value"); diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index adbf20292d..b32d8bba9e 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -54,7 +54,7 @@ namespace MWLua { } MWBase::LuaManager::ActorControls mControls; - std::map mStatsCache; + std::map mStatsCache; bool mIsActive; }; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5fa2d9867c..780ddaf9a9 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -343,7 +343,6 @@ namespace MWLua mPlayerStorage.clearTemporaryAndRemoveCallbacks(); mInputActions.clear(); mInputTriggers.clear(); - mQueuedAutoStartedScripts.clear(); for (int i = 0; i < 5; ++i) lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0); } @@ -654,8 +653,8 @@ namespace MWLua MWBase::Environment::get().getL10nManager()->dropCache(); mUiResourceManager.clear(); mLua.dropScriptCache(); - mInputActions.clear(true); - mInputTriggers.clear(true); + mInputActions.clear(); + mInputTriggers.clear(); initConfiguration(); ESM::LuaScripts globalData; diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 19dada74a7..7259d03f4c 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -1061,7 +1061,7 @@ namespace MWLua }; // types.Actor.activeEffects(o):removeEffect(id, ?arg) - activeEffectsT["remove"] = [getEffectKey, context](const ActorActiveEffects& effects, std::string_view idStr, + activeEffectsT["remove"] = [getEffectKey](const ActorActiveEffects& effects, std::string_view idStr, sol::optional argStr) { if (!effects.isActor()) return; @@ -1071,14 +1071,12 @@ namespace MWLua MWMechanics::EffectKey key = getEffectKey(idStr, argStr); - context.mLuaManager->addAction([key, effects]() { - // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a - // spell), we still need to use the active spells store to purge this effect from active spells. - const auto& ptr = effects.mActor.ptr(); + // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a + // spell), we still need to use the active spells store to purge this effect from active spells. + const auto& ptr = effects.mActor.ptr(); - auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); - activeSpells.purgeEffect(ptr, key.mId, key.mArg); - }); + auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); + activeSpells.purgeEffect(ptr, key.mId, key.mArg); }; // types.Actor.activeEffects(o):set(value, id, ?arg) diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index a6d762499a..df317ffeba 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -213,7 +213,6 @@ namespace MWLua { "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound }, { "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound }, { "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound }, - { "TargetPolygonNotFound", DetourNavigator::Status::TargetPolygonNotFound }, { "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed }, { "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed }, { "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed }, diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 637ae0b002..317ffbd406 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -123,8 +123,7 @@ namespace MWLua SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] - = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value; } }; @@ -160,7 +159,7 @@ namespace MWLua SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] - = sol::main_object(value); + = value; } }; @@ -184,8 +183,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] - = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value; } sol::object getProgress(const Context& context) const @@ -206,8 +204,7 @@ namespace MWLua SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] - = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value; } SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const @@ -261,7 +258,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value; } static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) @@ -321,7 +318,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = value; } static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) @@ -405,7 +402,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value; } static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) @@ -468,8 +465,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] - = sol::main_object(value); + obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] = value; } static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) diff --git a/apps/openmw/mwlua/types/levelledlist.cpp b/apps/openmw/mwlua/types/levelledlist.cpp index 91c3a078a4..fd848d9121 100644 --- a/apps/openmw/mwlua/types/levelledlist.cpp +++ b/apps/openmw/mwlua/types/levelledlist.cpp @@ -43,7 +43,7 @@ namespace MWLua [](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); }); record["chanceNone"] = sol::readonly_property( [](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); }); - record["creatures"] = sol::readonly_property([state](const ESM::CreatureLevList& rec) -> sol::table { + record["creatures"] = sol::readonly_property([&](const ESM::CreatureLevList& rec) -> sol::table { sol::table res(state, sol::create); for (size_t i = 0; i < rec.mList.size(); ++i) res[LuaUtil::toLuaIndex(i)] = rec.mList[i]; diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 7fbfd5fb41..d5c52c8c4f 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -63,7 +63,7 @@ namespace if (rec["type"] != sol::nil) { int weaponType = rec["type"].get(); - if (weaponType >= 0 && weaponType <= ESM::Weapon::Last) + if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown) weapon.mData.mType = weaponType; else throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 677a51a887..9652df1238 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -168,8 +168,7 @@ namespace MWLua if (index == LuaUi::Layer::count()) throw std::logic_error(std::string("Layer not found")); index++; - context.mLuaManager->addAction( - [=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; layersTable["insertBefore"] = [context]( std::string_view beforename, std::string_view name, const sol::object& opt) { @@ -178,8 +177,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( - [=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; sol::table layers = LuaUtil::makeReadOnly(layersTable); sol::table layersMeta = layers[sol::metatable_key]; @@ -287,9 +285,8 @@ namespace MWLua return res; }; api["_setWindowDisabled"] - = [windowManager, luaManager = context.mLuaManager](std::string window, bool disabled) { - luaManager->addAction( - [=, window = std::move(window)]() { windowManager->setDisabledByLua(window, disabled); }); + = [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) { + luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); }); }; // TODO @@ -311,7 +308,7 @@ namespace MWLua return res.str(); }; element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, - [](LuaUi::Element& element, const sol::main_table& layout) { element.mLayout = layout; }); + [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { if (element->mState != LuaUi::Element::Created) return; diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index 330bf871e7..cf1fa02815 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -68,7 +68,7 @@ namespace MWLua Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'."; } - sol::object readFile(lua_State* lua, FileHandle& file) + sol::object readFile(sol::this_state lua, FileHandle& file) { std::ostringstream os; if (file.mFilePtr && file.mFilePtr->peek() != EOF) @@ -79,7 +79,7 @@ namespace MWLua return sol::make_object(lua, std::move(result)); } - sol::object readLineFromFile(lua_State* lua, FileHandle& file) + sol::object readLineFromFile(sol::this_state lua, FileHandle& file) { std::string result; if (file.mFilePtr && std::getline(*file.mFilePtr, result)) @@ -91,7 +91,7 @@ namespace MWLua return sol::nil; } - sol::object readNumberFromFile(lua_State* lua, Files::IStreamPtr& file) + sol::object readNumberFromFile(sol::this_state lua, Files::IStreamPtr& file) { double number = 0; if (file && *file >> number) @@ -100,7 +100,7 @@ namespace MWLua return sol::nil; } - sol::object readCharactersFromFile(lua_State* lua, FileHandle& file, size_t count) + sol::object readCharactersFromFile(sol::this_state lua, FileHandle& file, size_t count) { if (count <= 0 && file.mFilePtr->peek() != EOF) return sol::make_object(lua, std::string()); @@ -189,7 +189,7 @@ namespace MWLua return seek(lua, self, std::ios_base::cur, off); }); - handle["lines"] = [](sol::this_main_state lua, sol::main_object self) { + handle["lines"] = [](sol::this_state lua, sol::object self) { if (!self.is()) throw std::runtime_error("self should be a file handle"); return sol::as_function([lua, self]() -> sol::object { @@ -199,7 +199,7 @@ namespace MWLua }); }; - api["lines"] = [vfs](sol::this_main_state lua, std::string_view fileName) { + api["lines"] = [vfs](sol::this_state lua, std::string_view fileName) { auto normalizedName = VFS::Path::normalizeFilename(fileName); return sol::as_function( [lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable { diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9e64996e46..3cee16f29f 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -289,9 +289,8 @@ namespace MWMechanics const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot); if (enchantmentId.empty()) continue; - const ESM::Enchantment* enchantment - = world->getStore().get().search(enchantmentId); - if (enchantment == nullptr || enchantment->mData.mType != ESM::Enchantment::ConstantEffect) + 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) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1e62cc4a21..bb3273981d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2028,7 +2028,7 @@ namespace MWMechanics iter->second->getCharacterController().skipAnim(); } - bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const + bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3e34ed0d67..b575ec2827 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -119,7 +119,7 @@ namespace MWMechanics std::string_view startKey, std::string_view stopKey, bool forceLoop); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr) const; - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const; + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const; void persistAnimationStates() const; void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 42667a406a..3c299c1490 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -40,15 +40,15 @@ namespace MWMechanics static const std::size_t MAX_IDLE_SIZE = 8; - const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { - "idle2", - "idle3", - "idle4", - "idle5", - "idle6", - "idle7", - "idle8", - "idle9", + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { + std::string("idle2"), + std::string("idle3"), + std::string("idle4"), + std::string("idle5"), + std::string("idle6"), + std::string("idle7"), + std::string("idle8"), + std::string("idle9"), }; namespace @@ -680,7 +680,7 @@ namespace MWMechanics { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { - const std::string_view groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; + const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); } else @@ -695,7 +695,7 @@ namespace MWMechanics { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { - const std::string_view groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; + const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName); } else diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 01a02096a4..aed7214f4d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -3,7 +3,6 @@ #include "typedaipackage.hpp" -#include #include #include "aitemporarybase.hpp" @@ -182,7 +181,9 @@ namespace MWMechanics const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); /// lookup table for converting idleSelect value to groupName - static const std::string_view sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; + static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; + + static int OffsetToPreventOvercrowding(); }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d13f3c9926..c22308bce4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1104,10 +1104,10 @@ namespace MWMechanics attackType = ESM::Weapon::AT_Thrust; // We want to avoid hit keys that come out of nowhere (e.g. in the follow animation) // and processing multiple hit keys for a single attack - if (mReadyToHit) + if (mAttackStrength != -1.f) { charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess); - mReadyToHit = false; + mAttackStrength = -1.f; } } else if (isRandomAttackAnimation(groupname) && action == "start") @@ -1153,10 +1153,10 @@ namespace MWMechanics else if (action == "shoot release") { // See notes for melee release above - if (mReadyToHit) + if (mAttackStrength != -1.f) { mAnimation->releaseArrow(mAttackStrength); - mReadyToHit = false; + mAttackStrength = -1.f; } } else if (action == "shoot follow attach") @@ -1246,7 +1246,7 @@ namespace MWMechanics void CharacterController::prepareHit() { - if (mReadyToHit) + if (mAttackStrength != -1.f) return; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); @@ -1261,8 +1261,6 @@ namespace MWMechanics mAttackStrength = 0.f; playSwishSound(); } - - mReadyToHit = true; } bool CharacterController::updateWeaponState() @@ -1522,7 +1520,6 @@ namespace MWMechanics && (mHitState == CharState_None || mHitState == CharState_Block)) { mAttackStrength = -1.f; - mReadyToHit = false; // Randomize attacks for non-bipedal creatures if (!cls.isBipedal(mPtr) @@ -1809,7 +1806,8 @@ namespace MWMechanics stop = strength + ' ' + stop; } - mReadyToHit = false; + // Reset attack strength to make extra sure hits that come out of nowhere aren't processed + mAttackStrength = -1.f; if (animPlaying) mAnimation->disable(mCurrentWeapon); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d5c642c883..f043419a81 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -172,7 +172,6 @@ namespace MWMechanics std::string mCurrentWeapon; float mAttackStrength{ -1.f }; - bool mReadyToHit{ false }; MWWorld::Ptr mAttackVictim; osg::Vec3f mAttackHitPos; bool mAttackSuccess{ false }; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index e7c7342284..5d283214a3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -578,24 +578,6 @@ namespace MWMechanics return dist; } - float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon) - { - MWBase::World* world = MWBase::Environment::get().getWorld(); - const MWWorld::Store& store = world->getStore().get(); - const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); - if (!weapon.isEmpty()) - return fCombatDistance * weapon.get()->mBase->mData.mReach; - if (actor.getClass().isNpc()) - return fCombatDistance * store.find("fHandToHandReach")->mValue.getFloat(); - return fCombatDistance; - } - - bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach) - { - const float heightDiff = actor.getRefData().getPosition().pos[2] - target.getRefData().getPosition().pos[2]; - return std::abs(heightDiff) < reach && getDistanceToBounds(actor, target) < reach; - } - std::pair getHitContact(const MWWorld::Ptr& actor, float reach) { // Lasciate ogne speranza, voi ch'entrate @@ -632,12 +614,10 @@ namespace MWMechanics { if (actor == target || target.getClass().getCreatureStats(target).isDead()) continue; - const float dist = getDistanceToBounds(actor, target); - if (dist >= minDist || !isInMeleeReach(actor, target, reach)) - continue; - const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach) + continue; // Horizontal angle checks. osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index fa3660a016..92033c7e77 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -64,10 +64,6 @@ namespace MWMechanics // Cursed distance calculation used for combat proximity and hit checks in Morrowind float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon); - - bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach); - // Similarly cursed hit target selection std::pair getHitContact(const MWWorld::Ptr& actor, float reach); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 47c49a8861..46f6440ae6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -778,8 +778,7 @@ namespace MWMechanics else mObjects.skipAnimation(ptr); } - - bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) + bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { if (ptr.getClass().isActor()) return mActors.checkAnimationPlaying(ptr, groupName); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 93af89863b..4b0126cd34 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -146,7 +146,7 @@ namespace MWMechanics std::string_view startKey, std::string_view stopKey, bool forceLoop) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void skipAnimation(const MWWorld::Ptr& ptr) override; - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) override; + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override; void persistAnimationStates() override; void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 05c722f486..f4bafdb48b 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -161,7 +161,7 @@ namespace MWRender bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const { - if (Settings::game().mShieldSheathing && mObjectRoot) + if (Settings::game().mShieldSheathing) { const MWWorld::Class& cls = mPtr.getClass(); MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f07a325f7c..0a2ef7bef8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1695,7 +1695,7 @@ namespace MWRender mGlowUpdater->setColor(color); mGlowUpdater->setDuration(glowDuration); } - else if (mObjectRoot) + else mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); } } @@ -1869,7 +1869,7 @@ namespace MWRender void Animation::setAlpha(float alpha) { - if (alpha == mAlpha || !mObjectRoot) + if (alpha == mAlpha) return; mAlpha = alpha; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 18604e78ca..123eadfdec 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -260,6 +260,7 @@ namespace MWRender // turn off sky blending stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index fb839cfa3a..f84e58e4bc 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -259,8 +259,7 @@ namespace MWRender void CreatureWeaponAnimation::addControllers() { Animation::addControllers(); - if (mObjectRoot) - WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); + WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index e582bb76ee..968903f6a3 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -736,6 +736,7 @@ namespace MWRender // turn of sky blending stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); osg::ref_ptr lightmodel = new osg::LightModel; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 4847da9cee..8fdc0a45ca 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -762,18 +762,14 @@ namespace MWRender if (Misc::StringUtils::ciEqual(technique->getName(), name)) return technique; - std::string realName = name; - auto fileIter = mTechniqueFileMap.find(name); - if (fileIter != mTechniqueFileMap.end()) - realName = fileIter->first; - auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), - std::move(realName), renderWidth(), renderHeight(), mUBO, mNormalsSupported); + name, renderWidth(), renderHeight(), mUBO, mNormalsSupported); technique->compile(); if (technique->getStatus() != fx::Technique::Status::File_Not_exists) - technique->setLastModificationTime(std::filesystem::last_write_time(fileIter->second)); + technique->setLastModificationTime( + std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()])); if (loadNextFrame) { diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index af6eeae62b..2630467f95 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "pingpongcanvas.hpp" #include "transparentpass.hpp" @@ -230,8 +229,7 @@ namespace MWRender TechniqueList mQueuedTemplates; TechniqueList mInternalTechniques; - std::unordered_map - mTechniqueFileMap; + std::unordered_map mTechniqueFileMap; RenderingManager& mRendering; osgViewer::Viewer* mViewer; diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index ab982d0c55..bb8248217a 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -49,6 +49,11 @@ namespace MWRender && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; #endif + if (mUseCompute) + Log(Debug::Info) << "Initialized compute shader pipeline for water ripples"; + else + Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples"; + for (size_t i = 0; i < mState.size(); ++i) { osg::ref_ptr stateset = new osg::StateSet; @@ -86,18 +91,6 @@ namespace MWRender else setupFragmentPipeline(); - if (mProgramBlobber != nullptr) - { - static bool pipelineLogged = [&] { - if (mUseCompute) - Log(Debug::Info) << "Initialized compute shader pipeline for water ripples"; - else - Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples"; - return true; - }(); - (void)pipelineLogged; - } - setCullCallback(new osg::NodeCallback); setUpdateCallback(new osg::NodeCallback); } @@ -109,36 +102,21 @@ namespace MWRender Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); - osg::ref_ptr blobber - = shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT); - osg::ref_ptr simulate - = shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT); - if (vertex == nullptr || blobber == nullptr || simulate == nullptr) - { - Log(Debug::Error) << "Failed to load shaders required for fragment shader ripple pipeline"; - return; - } - mProgramBlobber = shaderManager.getProgram(vertex, std::move(blobber)); - mProgramSimulation = shaderManager.getProgram(std::move(vertex), std::move(simulate)); + mProgramBlobber = shaderManager.getProgram( + vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); + mProgramSimulation = shaderManager.getProgram( + std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); } void RipplesSurface::setupComputePipeline() { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr blobber - = shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE); - osg::ref_ptr simulate - = shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE); - if (blobber == nullptr || simulate == nullptr) - { - Log(Debug::Error) << "Failed to load shaders required for compute shader ripple pipeline"; - return; - } - - mProgramBlobber = shaderManager.getProgram(nullptr, std::move(blobber)); - mProgramSimulation = shaderManager.getProgram(nullptr, std::move(simulate)); + mProgramBlobber = shaderManager.getProgram( + nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE)); + mProgramSimulation = shaderManager.getProgram( + nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); } void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) @@ -206,9 +184,6 @@ namespace MWRender void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const { - if (mProgramBlobber == nullptr || mProgramSimulation == nullptr) - return; - osg::State& state = *renderInfo.getState(); const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const State& frameState = mState[currentFrame]; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b601c0dd61..1cc8cc0d3e 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -262,15 +262,15 @@ namespace MWRender , mPrecipitationAlpha(0.f) , mDirtyParticlesEffect(false) { - mSkyRootNode = new CameraRelativeTransform; - mSkyRootNode->setName("Sky Root"); + 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()) - mSkyRootNode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - mSceneManager->setUpNormalsRTForStateSet(mSkyRootNode->getOrCreateStateSet(), false); - SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*mSkyRootNode->getOrCreateStateSet()); - parentNode->addChild(mSkyRootNode); + mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); + parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; // render before the world is rendered @@ -281,18 +281,19 @@ namespace MWRender if (enableSkyRTT) { mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot); - mSkyRootNode->addChild(mSkyRTT); + skyroot->addChild(mSkyRTT); + mRootNode = new osg::Group; + skyroot->addChild(mRootNode); } + else + mRootNode = skyroot; - mSkyNode = new osg::Group; - mSkyNode->setNodeMask(Mask_Sky); - mSkyNode->addChild(mEarlyRenderBinRoot); - mSkyRootNode->addChild(mSkyNode); - - mUnderwaterSwitch = new UnderwaterSwitchCallback(mSkyRootNode); + mRootNode->setNodeMask(Mask_Sky); + mRootNode->addChild(mEarlyRenderBinRoot); + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion; - mPrecipitationOccluder = std::make_unique(mSkyRootNode, parentNode, rootNode, camera); + mPrecipitationOccluder = std::make_unique(skyroot, parentNode, rootNode, camera); } void SkyManager::create() @@ -463,7 +464,7 @@ namespace MWRender mRainParticleSystem->setUserValue("particleOcclusion", true); mSceneManager->recreateShaders(mRainNode); - mSkyNode->addChild(mRainNode); + mRootNode->addChild(mRainNode); if (mPrecipitationOcclusion) mPrecipitationOccluder->enable(); } @@ -473,7 +474,7 @@ namespace MWRender if (!mRainNode) return; - mSkyNode->removeChild(mRainNode); + mRootNode->removeChild(mRainNode); mRainNode = nullptr; mPlacer = nullptr; mCounter = nullptr; @@ -484,10 +485,10 @@ namespace MWRender SkyManager::~SkyManager() { - if (mSkyRootNode) + if (mRootNode) { - mSkyRootNode->getParent(0)->removeChild(mSkyRootNode); - mSkyRootNode = nullptr; + mRootNode->getParent(0)->removeChild(mRootNode); + mRootNode = nullptr; } } @@ -594,7 +595,7 @@ namespace MWRender const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u; mEarlyRenderBinRoot->setNodeMask(mask); - mSkyNode->setNodeMask(mask); + mRootNode->setNodeMask(mask); if (!enabled && mParticleNode && mParticleEffect) { @@ -690,7 +691,7 @@ namespace MWRender { if (mParticleNode) { - mSkyNode->removeChild(mParticleNode); + mRootNode->removeChild(mParticleNode); mParticleNode = nullptr; } if (mRainEffect.empty()) @@ -705,7 +706,7 @@ namespace MWRender mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->setNodeMask(Mask_WeatherParticles); - mSkyNode->addChild(mParticleNode); + mRootNode->addChild(mParticleNode); } mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 6d33165c36..6a32978c4e 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -118,8 +118,7 @@ namespace MWRender osg::Camera* mCamera; - osg::ref_ptr mSkyRootNode; - osg::ref_ptr mSkyNode; + osg::ref_ptr mRootNode; osg::ref_ptr mEarlyRenderBinRoot; osg::ref_ptr mParticleNode; diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 0f9d85cc38..c248c30520 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -162,17 +162,11 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::Ptr ptr = R()(runtime); int index = runtime[0].mInteger; runtime.pop(); - if (ptr.isEmpty()) - { - runtime.push(0); - return; - } - bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying( ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index))); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index f829ea458a..3c3d6fb26e 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -417,15 +417,19 @@ namespace MWSound { { const std::lock_guard openLock(mOutput.mReopenMutex); - std::basic_string_view defaultName = getDeviceName(nullptr); + auto defaultName = getDeviceName(nullptr); if (mCurrentName != defaultName) { - mCurrentName = defaultName; - Log(Debug::Info) << "Default audio device changed to \"" << mCurrentName << "\""; - ALCboolean reopened = alcReopenDeviceSOFT( - mOutput.mDevice, mCurrentName.data(), mOutput.mContextAttributes.data()); + Log(Debug::Info) << "Default audio device changed"; + ALCboolean reopened + = alcReopenDeviceSOFT(mOutput.mDevice, nullptr, mOutput.mContextAttributes.data()); if (reopened == AL_FALSE) + { + mCurrentName = defaultName; Log(Debug::Warning) << "Failed to switch to new audio device"; + } + else + mCurrentName = getDeviceName(mOutput.mDevice); } } mCondVar.wait_for(lock, std::chrono::seconds(2)); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 32f0fe0aef..c41ddd42c0 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -38,7 +38,7 @@ MWState::Character* MWState::CharacterManager::getCurrentCharacter() return mCurrent; } -void MWState::CharacterManager::deleteSlot(const MWState::Slot* slot, const MWState::Character*& character) +void MWState::CharacterManager::deleteSlot(const MWState::Character* character, const MWState::Slot* slot) { std::list::iterator it = findCharacter(character); @@ -51,7 +51,6 @@ void MWState::CharacterManager::deleteSlot(const MWState::Slot* slot, const MWSt if (character == mCurrent) mCurrent = nullptr; mCharacters.erase(it); - character = nullptr; } } diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index 015144d820..dac189e68a 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -33,7 +33,7 @@ namespace MWState Character* getCurrentCharacter(); ///< @note May return null - void deleteSlot(const MWState::Slot* slot, const Character*& character); + void deleteSlot(const MWState::Character* character, const MWState::Slot* slot); Character* createCharacter(const std::string& name); ///< Create new character within saved game management diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index b997d124c8..9e292a3eee 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -706,10 +706,10 @@ void MWState::StateManager::quickLoad() void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) { const std::filesystem::path savePath = slot->mPath; - mCharacterManager.deleteSlot(slot, character); + mCharacterManager.deleteSlot(character, slot); if (mLastSavegame == savePath) { - if (character != nullptr) + if (character->begin() != character->end()) mLastSavegame = character->begin()->mPath; else mLastSavegame.clear(); @@ -757,14 +757,12 @@ void MWState::StateManager::update(float duration) if (mNewGameRequest) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu); newGame(); mNewGameRequest = false; } if (mLoadRequest) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu); loadGame(*mLoadRequest); mLoadRequest = std::nullopt; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index fca0135e13..4cd189bb20 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -337,13 +337,13 @@ namespace // helper function for forEachInternal template - bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore& cellStore, bool includeDeleted) + bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore* cellStore) { - for (auto& v : list.mList) + for (typename List::List::iterator iter(list.mList.begin()); iter != list.mList.end(); ++iter) { - if (!includeDeleted && !MWWorld::CellStore::isAccessible(v.mData, v.mRef)) + if (!MWWorld::CellStore::isAccessible(iter->mData, iter->mRef)) continue; - if (!visitor(MWWorld::Ptr(&v, &cellStore))) + if (!visitor(MWWorld::Ptr(&*iter, cellStore))) return false; } return true; @@ -399,12 +399,12 @@ namespace MWWorld // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved // objects are accounted for. template - static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore, bool includeDeleted) + static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore) { bool returnValue = true; - Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&](auto& store) { - returnValue = returnValue && forEachImp(visitor, store, cellStore, includeDeleted); + Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) { + returnValue = returnValue && forEachImp(visitor, store, &cellStore); }); return returnValue; @@ -583,11 +583,11 @@ namespace MWWorld mMergedRefsNeedsUpdate = true; } - void CellStore::updateMergedRefs(bool includeDeleted) const + void CellStore::updateMergedRefs() const { mMergedRefs.clear(); MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); - CellStoreImp::forEachInternal(visitor, const_cast(*this), includeDeleted); + CellStoreImp::forEachInternal(visitor, const_cast(*this)); visitor.merge(); mMergedRefsNeedsUpdate = false; } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 126935ace5..097053e2e0 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -213,13 +213,13 @@ namespace MWWorld /// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template - bool forEach(Visitor&& visitor, bool includeDeleted = false) + bool forEach(Visitor&& visitor) { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) - updateMergedRefs(includeDeleted); + updateMergedRefs(); if (mMergedRefs.empty()) return true; @@ -227,7 +227,7 @@ namespace MWWorld for (LiveCellRefBase* mergedRef : mMergedRefs) { - if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; if (!visitor(MWWorld::Ptr(mergedRef, this))) @@ -242,17 +242,17 @@ namespace MWWorld /// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template - bool forEachConst(Visitor&& visitor, bool includeDeleted = false) const + bool forEachConst(Visitor&& visitor) const { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) - updateMergedRefs(includeDeleted); + updateMergedRefs(); for (const LiveCellRefBase* mergedRef : mMergedRefs) { - if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; if (!visitor(MWWorld::ConstPtr(mergedRef, this))) @@ -267,25 +267,28 @@ namespace MWWorld /// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template - bool forEachType(Visitor&& visitor, bool includeDeleted = false) + bool forEachType(Visitor&& visitor) { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) - updateMergedRefs(includeDeleted); + updateMergedRefs(); if (mMergedRefs.empty()) return true; mHasState = true; - for (LiveCellRefBase& base : get().mList) + CellRefList& list = get(); + + for (typename CellRefList::List::iterator it(list.mList.begin()); it != list.mList.end(); ++it) { - if (mMovedToAnotherCell.contains(&base)) + LiveCellRefBase* base = &*it; + if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) continue; - if (!includeDeleted && !isAccessible(base.mData, base.mRef)) + if (!isAccessible(base->mData, base->mRef)) continue; - if (!visitor(MWWorld::Ptr(&base, this))) + if (!visitor(MWWorld::Ptr(base, this))) return false; } @@ -403,7 +406,7 @@ namespace MWWorld /// Repopulate mMergedRefs. void requestMergedRefsUpdate(); - void updateMergedRefs(bool includeDeleted = false) const; + void updateMergedRefs() const; // (item, max charge) typedef std::vector> TRechargingItems; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 6bfbd78493..fb2722dde8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -406,7 +406,8 @@ namespace MWWorld template ContainerStoreIteratorBase(const ContainerStoreIteratorBase& other) { - static_assert(IsConvertible::value); + char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible::value ? 1 : -1]; + ((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR); copy(other); } diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 833f152e13..ebdd40303a 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -70,7 +70,7 @@ namespace MWWorld mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr); reader.setModIndex(index); reader.updateModIndices(mNameToIndex); - mStore.loadESM4(reader, listener); + mStore.loadESM4(reader); break; } } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index ea183b6b53..15a687f4d7 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -468,16 +468,9 @@ namespace MWWorld } } - void ESMStore::loadESM4(ESM4::Reader& reader, Loading::Listener* listener) + void ESMStore::loadESM4(ESM4::Reader& reader) { - if (listener != nullptr) - listener->setProgressRange(::EsmLoader::fileProgress); - auto visitorRec = [this, listener](ESM4::Reader& reader) { - bool result = ESMStoreImp::readRecord(reader, *this); - if (listener != nullptr) - listener->setProgress(::EsmLoader::fileProgress * reader.getFileOffset() / reader.getFileSize()); - return result; - }; + auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); }; ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 6c71ae0052..d8cfd1dcdf 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -219,7 +219,7 @@ namespace MWWorld void validateDynamic(); void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); - void loadESM4(ESM4::Reader& esm, Loading::Listener* listener); + void loadESM4(ESM4::Reader& esm); template const Store& get() const diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fb3aee958c..c30c2b5af0 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -367,7 +367,7 @@ namespace MWWorld ListAndResetObjectsVisitor visitor; - cell->forEach(visitor, true); // Include objects being teleported by Lua + cell->forEach(visitor); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 57d794c535..7b2f178343 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -290,14 +290,14 @@ namespace MWWorld mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); } - void World::init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr rootNode, - SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue) + void World::init(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, SceneUtil::WorkQueue* workQueue, + SceneUtil::UnrefQueue& unrefQueue) { mPhysics = std::make_unique(mResourceSystem, rootNode); if (Settings::navigator().mEnable) { - auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(maxRecastLogLevel); + auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 983682a98f..6f06812e20 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -202,8 +201,8 @@ namespace MWWorld Loading::Listener* listener); // Must be called after `loadData`. - void init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr rootNode, - SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue); + void init(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, SceneUtil::WorkQueue* workQueue, + SceneUtil::UnrefQueue& unrefQueue); virtual ~World(); diff --git a/apps/openmw_tests/main.cpp b/apps/openmw_tests/main.cpp index 485298c863..6b7298596a 100644 --- a/apps/openmw_tests/main.cpp +++ b/apps/openmw_tests/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include @@ -25,9 +24,5 @@ int main(int argc, char* argv[]) Settings::StaticValues::init(); testing::InitGoogleTest(&argc, argv); - - const int result = RUN_ALL_TESTS(); - if (result == 0) - std::filesystem::remove_all(TestingOpenMW::outputDir()); - return result; + return RUN_ALL_TESTS(); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7659de0ffd..b6734e6fc6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -59,7 +59,7 @@ list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE} add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box inputactions yamlloader scripttracker luastateptr + shapes/box inputactions yamlloader scripttracker ) add_component_dir (l10n diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 36373f8f35..3210716a5c 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -1,7 +1,6 @@ #include "gamesettings.hpp" #include -#include #include #include @@ -38,13 +37,8 @@ void Config::GameSettings::validatePaths() mDataDirs.clear(); - QProgressDialog progressBar("Validating paths", {}, 0, paths.count() + 1); - progressBar.setWindowModality(Qt::WindowModal); - progressBar.setValue(0); - for (const auto& dataDir : paths) { - progressBar.setValue(progressBar.value() + 1); if (QDir(dataDir.value).exists()) { SettingValue copy = dataDir; @@ -56,8 +50,6 @@ void Config::GameSettings::validatePaths() // Do the same for data-local const QString& local = mSettings.value(QString("data-local")).value; - progressBar.setValue(progressBar.value() + 1); - if (!local.isEmpty() && QDir(local).exists()) { mDataLocal = QDir(local).canonicalPath(); @@ -282,8 +274,9 @@ bool Config::GameSettings::isOrderedLine(const QString& line) // - Always ignore a line beginning with '#' or empty lines; added above a config // entry. // -// - If a line in file exists with matching key and value, then replace the line with that of mUserSettings. -// - else if only the key matches, remove comment +// - If a line in file exists with matching key and first part of value (before ',', +// '\n', etc) also matches, then replace the line with that of mUserSettings. +// - else remove line // // - If there is no corresponding line in file, add at the end // @@ -309,25 +302,6 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) if (fileCopy.empty()) return writeFile(stream); - QMultiMap existingSettings; - QString context = QFileInfo(file).absoluteDir().path(); - if (readFile(stream, existingSettings, context)) - { - // don't use QMultiMap operator== as mUserSettings may have blank context fields - // don't use one std::equal with custom predicate as (until Qt 6.4) there was no key-value iterator - if (std::equal(existingSettings.keyBegin(), existingSettings.keyEnd(), mUserSettings.keyBegin(), - mUserSettings.keyEnd()) - && std::equal(existingSettings.cbegin(), existingSettings.cend(), mUserSettings.cbegin(), - [](const SettingValue& l, const SettingValue& r) { - return l.originalRepresentation == r.originalRepresentation; - })) - { - // The existing file already contains what we need, don't risk scrambling comments and formatting - return true; - } - } - stream.seek(0); - // start // | // | +----------------------------------------------------------+ @@ -351,7 +325,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) // +----------------------------------------------------------+ // // - QRegularExpression settingRegex("^([^=]+)\\s*=\\s*(.+?)\\s*$"); + QRegularExpression settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$"); std::vector comments; auto commentStart = fileCopy.end(); std::map> commentsMap; @@ -421,7 +395,8 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) // look for a key in the line if (!match.hasMatch() || settingRegex.captureCount() < 2) { - // no key or no value found in line, replace with a null string which will be removed later + // no key or first part of value found in line, replace with a null string which + // will be removed later *iter = QString(); comments.clear(); commentStart = fileCopy.end(); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5f0d41d38c..fe26d37b97 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -79,10 +78,14 @@ ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(int row) } const ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(const QString& name) const { - bool path = name.contains('/'); + EsmFile::FileProperty fp = EsmFile::FileProperty_FileName; + + if (name.contains('/')) + fp = EsmFile::FileProperty_FilePath; + for (const EsmFile* file : mFiles) { - if (name.compare(path ? file->filePath() : file->fileName(), Qt::CaseInsensitive) == 0) + if (name.compare(file->fileProperty(fp).toString(), Qt::CaseInsensitive) == 0) return file; } return nullptr; @@ -307,6 +310,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { setCheckState(file->filePath(), success); emit dataChanged(index, index); + checkForLoadOrderErrors(); } else return success; @@ -421,6 +425,7 @@ bool ContentSelectorModel::ContentModel::dropMimeData( dataChanged(index(minRow, 0), index(maxRow, 0)); // at this point we know that drag and drop has finished. + checkForLoadOrderErrors(); return true; } @@ -547,13 +552,15 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path) { + QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; - QDirIterator it(path, filters, QDir::Files | QDir::NoDotAndDotDot); - return it.hasNext(); + dir.setNameFilters(filters); + + return dir.entryList().count() != 0; } void ContentSelectorModel::ContentModel::clearFiles() @@ -700,13 +707,12 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const { - int index = indexFromItem(file).row(); - auto errors = checkForLoadOrderErrors(file, index); - return !errors.empty(); + return mPluginsWithLoadOrderError.contains(file->filePath()); } void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList) { + mPluginsWithLoadOrderError.clear(); int previousPosition = -1; for (const QString& filepath : fileList) { @@ -719,6 +725,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL if (filePosition < previousPosition) { mFiles.move(filePosition, previousPosition); + emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex())); } else { @@ -726,7 +733,24 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL } } } - emit dataChanged(index(0, 0), index(rowCount(), columnCount())); + checkForLoadOrderErrors(); +} + +void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() +{ + for (int row = 0; row < mFiles.count(); ++row) + { + EsmFile* file = mFiles.at(row); + bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; + if (isRowInError) + { + mPluginsWithLoadOrderError.insert(file->filePath()); + } + else + { + mPluginsWithLoadOrderError.remove(file->filePath()); + } + } } QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors( @@ -767,12 +791,11 @@ QList ContentSelectorModel::ContentModel:: QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const { - int index = indexFromItem(file).row(); - auto errors = checkForLoadOrderErrors(file, index); - if (!errors.empty()) + if (isLoadOrderError(file)) { QString text(""); - for (const LoadOrderError& error : errors) + int index = indexFromItem(item(file->filePath())).row(); + for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) { assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None); @@ -877,6 +900,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke void ContentSelectorModel::ContentModel::uncheckAll() { + emit layoutAboutToBeChanged(); mCheckedFiles.clear(); - emit dataChanged(index(0, 0), index(rowCount(), columnCount()), { Qt::CheckStateRole, Qt::UserRole + 1 }); + emit layoutChanged(); } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 3eba939125..467a9c032a 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -69,6 +69,9 @@ namespace ContentSelectorModel void refreshModel(); + /// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues + void checkForLoadOrderErrors(); + private: void addFile(EsmFile* file); @@ -86,6 +89,7 @@ namespace ContentSelectorModel QStringList mNonUserContent; std::set mCheckedFiles; QHash mNewFiles; + QSet mPluginsWithLoadOrderError; QString mEncoding; QIcon mWarningIcon; QIcon mErrorIcon; diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index d040dbd04d..28b4bd2822 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -48,18 +48,18 @@ namespace ContentSelectorModel void addGameFile(const QString& name) { mGameFiles.append(name); } QVariant fileProperty(const FileProperty prop) const; - const QString& fileName() const { return mFileName; } - const QString& author() const { return mAuthor; } + QString fileName() const { return mFileName; } + QString author() const { return mAuthor; } QDateTime modified() const { return mModified; } - const QString& formatVersion() const { return mVersion; } - const QString& filePath() const { return mPath; } + QString formatVersion() const { return mVersion; } + QString filePath() const { return mPath; } bool builtIn() const { return mBuiltIn; } bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } bool isMissing() const { return mPath.isEmpty(); } /// @note Contains file names, not paths. const QStringList& gameFiles() const { return mGameFiles; } - const QString& description() const { return mDescription; } + QString description() const { return mDescription; } QString toolTip() const { if (isMissing()) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 0be6e7c023..bce136b335 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -211,6 +211,7 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne ui->gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); + mContentModel->checkForLoadOrderErrors(); } void ContentSelectorView::ContentSelector::sortFiles() @@ -253,6 +254,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i oldIndex = index; setGameFileSelected(index, true); + mContentModel->checkForLoadOrderErrors(); } emit signalCurrentGamefileIndexChanged(index); diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index d9ef00ef77..23dfac549c 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -237,7 +237,7 @@ namespace Crash // must remain until monitor has finished waitMonitor(); - std::string message = "OpenMW has encountered a fatal error.\nCrash dump saved to '" + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + Misc::StringUtils::u8StringToString(getCrashDumpPath(*mShm).u8string()) + "'.\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_crashcatcher.hpp b/components/crashcatcher/windows_crashcatcher.hpp index bcf1ed688d..89678c9ada 100644 --- a/components/crashcatcher/windows_crashcatcher.hpp +++ b/components/crashcatcher/windows_crashcatcher.hpp @@ -21,7 +21,6 @@ namespace Crash // the main openmw process in task manager. static constexpr const int CrashCatcherTimeout = 2500; - static constexpr const int CrashCatcherThawTimeout = 250; struct CrashSHM; diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 8398528ec9..3708283efa 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -87,10 +87,9 @@ namespace Crash SetEvent(mSignalAppEvent); } - bool CrashMonitor::waitApp(bool thawMode) const + bool CrashMonitor::waitApp() const { - return WaitForSingleObject(mSignalMonitorEvent, thawMode ? CrashCatcherThawTimeout : CrashCatcherTimeout) - == WAIT_OBJECT_0; + return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0; } bool CrashMonitor::isAppAlive() const @@ -186,7 +185,7 @@ namespace Crash frozen = false; } - if (!mFreezeAbort && waitApp(frozen)) + if (!mFreezeAbort && waitApp()) { shmLock(); @@ -216,7 +215,7 @@ namespace Crash { handleCrash(true); TerminateProcess(mAppProcessHandle, 0xDEAD); - std::string message = "OpenMW has frozen.\nCrash dump saved to '" + std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + Misc::StringUtils::u8StringToString(getFreezeDumpPath(*mShm).u8string()) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); @@ -290,10 +289,10 @@ namespace Crash { std::thread messageBoxThread([&]() { SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" }; - SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW has frozen", - "OpenMW has frozen. This should never happen. Press Abort to terminate it and generate a crash dump to " - "help diagnose the problem.\nOpenMW may unfreeze if you wait, and this message box will disappear " - "after it becomes responsive.", + 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; diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 25ee710fd3..16e173169e 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -41,7 +41,7 @@ namespace Crash void signalApp() const; - bool waitApp(bool thawMode) const; + bool waitApp() const; bool isAppAlive() const; diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 2ba7b2072e..32aec8f0fc 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -106,96 +106,94 @@ namespace Debug logListener = std::move(listener); } - namespace + class DebugOutputBase : public boost::iostreams::sink { - class DebugOutputBase : public boost::iostreams::sink + public: + virtual std::streamsize write(const char* str, std::streamsize size) { - public: - virtual std::streamsize write(const char* str, std::streamsize size) + if (size <= 0) + return size; + std::string_view msg{ str, static_cast(size) }; + + // Skip debug level marker + Level level = All; + if (Log::sWriteLevel) { - if (size <= 0) - return size; - std::string_view msg{ str, static_cast(size) }; + level = getLevelMarker(msg[0]); + msg = msg.substr(1); + } - // Skip debug level marker - Level level = All; - if (Log::sWriteLevel) - { - level = getLevelMarker(msg[0]); - msg = msg.substr(1); - } - - char prefix[32]; - std::size_t prefixSize; - { - prefix[0] = '['; - const auto now = std::chrono::system_clock::now(); - const auto time = std::chrono::system_clock::to_time_t(now); - tm time_info{}; + char prefix[32]; + std::size_t prefixSize; + { + prefix[0] = '['; + const auto now = std::chrono::system_clock::now(); + const auto time = std::chrono::system_clock::to_time_t(now); + tm time_info{}; #ifdef _WIN32 - (void)localtime_s(&time_info, &time); + (void)localtime_s(&time_info, &time); #else - (void)localtime_r(&time, &time_info); + (void)localtime_r(&time, &time_info); #endif - prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", &time_info) + 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); - } - - 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); - if (logListener) - logListener( - level, std::string_view(prefix, prefixSize), std::string_view(msg.data(), lineSize)); - msg = msg.substr(lineSize); - } - - return size; + prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", &time_info) + 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); } - virtual ~DebugOutputBase() = default; - - protected: - static Level getLevelMarker(char marker) + while (!msg.empty()) { - if (0 <= marker && static_cast(marker) < static_cast(All)) - return static_cast(marker); - return All; + 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); + if (logListener) + logListener(level, std::string_view(prefix, prefixSize), std::string_view(msg.data(), lineSize)); + msg = msg.substr(lineSize); } - virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) - { - return size; - } - }; + return size; + } + + virtual ~DebugOutputBase() = default; + + protected: + static Level getLevelMarker(char marker) + { + if (0 <= marker && static_cast(marker) < static_cast(All)) + return static_cast(marker); + return All; + } + + virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) + { + return size; + } + }; #if defined _WIN32 && defined _DEBUG - class DebugOutput : public DebugOutputBase + class DebugOutput : public DebugOutputBase + { + public: + std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { - public: - std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) - { - // Make a copy for null termination - std::string tmp(str, static_cast(size)); - // Write string to Visual Studio Debug output - OutputDebugString(tmp.c_str()); - return size; - } + // Make a copy for null termination + std::string tmp(str, static_cast(size)); + // Write string to Visual Studio Debug output + OutputDebugString(tmp.c_str()); + return size; + } - virtual ~DebugOutput() = default; - }; + virtual ~DebugOutput() = default; + }; #else + namespace + { struct Record { std::string mValue; @@ -326,38 +324,22 @@ namespace Debug First mFirst; Second mSecond; }; + } #endif - Level toLevel(std::string_view value) - { - if (value == "ERROR") - return Error; - if (value == "WARNING") - return Warning; - if (value == "INFO") - return Info; - if (value == "VERBOSE") - return Verbose; - if (value == "DEBUG") - return Debug; - - return Verbose; - } - - static std::unique_ptr rawStdout = nullptr; - static std::unique_ptr rawStderr = nullptr; - static std::unique_ptr rawStderrMutex = nullptr; - static std::ofstream logfile; + static std::unique_ptr rawStdout = nullptr; + static std::unique_ptr rawStderr = nullptr; + static std::unique_ptr rawStderrMutex = nullptr; + static std::ofstream logfile; #if defined(_WIN32) && defined(_DEBUG) - static boost::iostreams::stream_buffer sb; + static boost::iostreams::stream_buffer sb; #else - static boost::iostreams::stream_buffer> standardOut; - static boost::iostreams::stream_buffer> standardErr; - static boost::iostreams::stream_buffer> bufferedOut; - static boost::iostreams::stream_buffer> bufferedErr; + static boost::iostreams::stream_buffer> standardOut; + static boost::iostreams::stream_buffer> standardErr; + static boost::iostreams::stream_buffer> bufferedOut; + static boost::iostreams::stream_buffer> bufferedErr; #endif - } std::ostream& getRawStdout() { @@ -377,19 +359,23 @@ namespace Debug Level getDebugLevel() { if (const char* env = getenv("OPENMW_DEBUG_LEVEL")) - return toLevel(env); + { + const std::string_view value(env); + if (value == "ERROR") + return Error; + if (value == "WARNING") + return Warning; + if (value == "INFO") + return Info; + if (value == "VERBOSE") + return Verbose; + if (value == "DEBUG") + return Debug; + } return Verbose; } - Level getRecastMaxLogLevel() - { - if (const char* env = getenv("OPENMW_RECAST_MAX_LOG_LEVEL")) - return toLevel(env); - - return Error; - } - void setupLogging(const std::filesystem::path& logDir, std::string_view appName) { Log::sMinDebugLevel = getDebugLevel(); diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index 2f9d7aa2d7..68cdec93a2 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -36,8 +36,6 @@ namespace Debug Level getDebugLevel(); - Level getRecastMaxLogLevel(); - // Redirect cout and cerr to the log file void setupLogging(const std::filesystem::path& logDir, std::string_view appName); diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 5f82c85cf9..24d7cc0d32 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -75,36 +74,6 @@ namespace DetourNavigator { return job.mGeneratedNavMeshData != nullptr; } - - std::string makeRevision(const Version& version) - { - return Misc::StringUtils::format(".%zu.%zu", version.mGeneration, version.mRevision); - } - - void writeDebugRecastMesh( - const Settings& settings, const TilePosition& tilePosition, const RecastMesh& recastMesh) - { - if (!settings.mEnableWriteRecastMeshToFile) - return; - std::string revision; - if (settings.mEnableRecastMeshFileNameRevision) - revision = makeRevision(recastMesh.getVersion()); - writeToFile(recastMesh, - Misc::StringUtils::format( - "%s%d.%d.", settings.mRecastMeshPathPrefix, tilePosition.x(), tilePosition.y()), - revision, settings.mRecast); - } - - void writeDebugNavMesh( - const Settings& settings, const GuardedNavMeshCacheItem& navMeshCacheItem, const Version& version) - { - if (!settings.mEnableWriteNavMeshToFile) - return; - std::string revision; - if (settings.mEnableNavMeshFileNameRevision) - revision = makeRevision(version); - writeToFile(navMeshCacheItem.lockConst()->getImpl(), settings.mNavMeshPathPrefix, revision); - } } std::ostream& operator<<(std::ostream& stream, JobStatus value) @@ -453,9 +422,9 @@ namespace DetourNavigator Misc::setCurrentThreadIdlePriority(); while (!mShouldStop) { - if (JobIt job = getNextJob(); job != mJobs.end()) + try { - try + if (JobIt job = getNextJob(); job != mJobs.end()) { const JobStatus status = processJob(*job); Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status @@ -480,20 +449,12 @@ namespace DetourNavigator } } } - catch (const std::exception& e) - { - Log(Debug::Warning) << "Failed to process navmesh job " << job->mId - << " for worldspace=" << job->mWorldspace << " agent=" << job->mAgentBounds - << " changedTile=(" << job->mChangedTile << ")" - << " changeType=" << job->mChangeType - << " by thread=" << std::this_thread::get_id() << ": " << e.what(); - unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); - removeJob(job); - } + else + cleanupLastUpdates(); } - else + catch (const std::exception& e) { - cleanupLastUpdates(); + Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what(); } } Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); @@ -501,8 +462,7 @@ namespace DetourNavigator JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Processing job " << job.mId << " for worldspace=" << job.mWorldspace - << " agent=" << job.mAgentBounds << "" + Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")" << " changedTile=(" << job.mChangedTile << ")" << " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id(); @@ -552,15 +512,6 @@ namespace DetourNavigator return JobStatus::Done; } - try - { - writeDebugRecastMesh(mSettings, job.mChangedTile, *recastMesh); - } - catch (const std::exception& e) - { - Log(Debug::Warning) << "Failed to write debug recast mesh: " << e.what(); - } - NavMeshTilesCache::Value cachedNavMeshData = mNavMeshTilesCache.get(job.mAgentBounds, job.mChangedTile, *recastMesh); std::unique_ptr preparedNavMeshData; @@ -682,19 +633,12 @@ namespace DetourNavigator mPresentTiles.insert(std::make_tuple(job.mAgentBounds, job.mChangedTile)); } - try - { - writeDebugNavMesh(mSettings, navMeshCacheItem, navMeshVersion); - } - catch (const std::exception& e) - { - Log(Debug::Warning) << "Failed to write debug navmesh: " << e.what(); - } + writeDebugFiles(job, &recastMesh); return isSuccess(status) ? JobStatus::Done : JobStatus::Fail; } - JobIt AsyncNavMeshUpdater::getNextJob() noexcept + JobIt AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); @@ -744,6 +688,31 @@ namespace DetourNavigator return job; } + void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const + { + std::string revision; + std::string recastMeshRevision; + std::string navMeshRevision; + if ((mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) + && (mSettings.get().mEnableRecastMeshFileNameRevision || mSettings.get().mEnableNavMeshFileNameRevision)) + { + revision = "." + + std::to_string((std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point()).count()); + if (mSettings.get().mEnableRecastMeshFileNameRevision) + recastMeshRevision = revision; + if (mSettings.get().mEnableNavMeshFileNameRevision) + navMeshRevision = std::move(revision); + } + if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) + writeToFile(*recastMesh, + mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + "_" + + 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); + } + bool AsyncNavMeshUpdater::lockTile( std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile) { @@ -769,7 +738,7 @@ namespace DetourNavigator return mJobs.size(); } - void AsyncNavMeshUpdater::cleanupLastUpdates() noexcept + void AsyncNavMeshUpdater::cleanupLastUpdates() { const auto now = std::chrono::steady_clock::now(); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 95919ed770..a4c446afc9 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -244,17 +244,19 @@ namespace DetourNavigator inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh); - inline JobIt getNextJob() noexcept; + JobIt getNextJob(); void postThreadJob(JobIt job, std::deque& queue); + void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; + bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); inline std::size_t getTotalJobs() const; - inline void cleanupLastUpdates() noexcept; + void cleanupLastUpdates(); inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener); diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 3a3430c9d3..5ce1464bdd 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -1,4 +1,5 @@ #include "debug.hpp" +#include "exceptions.hpp" #include "recastmesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" @@ -16,7 +17,7 @@ #include #include #include -#include +#include namespace DetourNavigator { @@ -223,8 +224,7 @@ namespace DetourNavigator const auto path = pathPrefix + "recastmesh" + revision + ".obj"; std::ofstream file(std::filesystem::path(path), std::ios::out); if (!file.is_open()) - throw std::system_error( - errno, std::generic_category(), "Failed to open file to write recast mesh: " + path); + throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); file.precision(std::numeric_limits::max_exponent10); std::vector vertices = recastMesh.getMesh().getVertices(); @@ -271,7 +271,7 @@ namespace DetourNavigator const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin"; std::ofstream file(std::filesystem::path(path), std::ios::out | std::ios::binary); if (!file.is_open()) - throw std::system_error(errno, std::generic_category(), "Failed to open file to write navmesh: " + path); + throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); NavMeshSetHeader header; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index fe63d27a1e..f037da69f8 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -523,7 +523,7 @@ namespace DetourNavigator std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings) { - RecastContext context(worldspace, tilePosition, agentBounds, recastMesh.getVersion(), settings.mMaxLogLevel); + RecastContext context(worldspace, tilePosition, agentBounds); const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentBounds.mHalfExtents.z(), settings); diff --git a/components/detournavigator/recastcontext.cpp b/components/detournavigator/recastcontext.cpp index 732517e423..225f251f4d 100644 --- a/components/detournavigator/recastcontext.cpp +++ b/components/detournavigator/recastcontext.cpp @@ -1,7 +1,7 @@ #include "recastcontext.hpp" #include "debug.hpp" -#include +#include "components/debug/debuglog.hpp" #include @@ -23,30 +23,25 @@ namespace DetourNavigator return Debug::Debug; } - std::string formatPrefix(ESM::RefId worldspace, const TilePosition& tilePosition, - const AgentBounds& agentBounds, const Version& version) + std::string formatPrefix( + ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds) { std::ostringstream stream; stream << "Worldspace: " << worldspace << "; tile position: " << tilePosition.x() << ", " - << tilePosition.y() << "; agent bounds: " << agentBounds << "; version: " << version << "; "; + << tilePosition.y() << "; agent bounds: " << agentBounds << "; "; return stream.str(); } } - RecastContext::RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition, - const AgentBounds& agentBounds, const Version& version, Debug::Level maxLogLevel) - : mMaxLogLevel(maxLogLevel) - , mPrefix(formatPrefix(worldspace, tilePosition, agentBounds, version)) + RecastContext::RecastContext( + ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds) + : mPrefix(formatPrefix(worldspace, tilePosition, agentBounds)) { } void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len) { - if (msg == nullptr || len <= 0) - return; - const Debug::Level level = getLogLevel(category); - if (level > mMaxLogLevel) - return; - Log(level) << mPrefix << std::string_view(msg, static_cast(len)); + if (len > 0) + Log(getLogLevel(category)) << mPrefix << std::string_view(msg, static_cast(len)); } } diff --git a/components/detournavigator/recastcontext.hpp b/components/detournavigator/recastcontext.hpp index b36c4b9842..8e75f50b34 100644 --- a/components/detournavigator/recastcontext.hpp +++ b/components/detournavigator/recastcontext.hpp @@ -3,7 +3,6 @@ #include "tileposition.hpp" -#include #include #include @@ -13,18 +12,15 @@ namespace DetourNavigator { struct AgentBounds; - struct Version; class RecastContext final : public rcContext { public: - explicit RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds, - const Version& version, Debug::Level maxLogLevel); + explicit RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds); const std::string& getPrefix() const { return mPrefix; } private: - Debug::Level mMaxLogLevel; std::string mPrefix; void doLog(rcLogCategory category, const char* msg, int len) override; diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index d71b3d12bc..5e555050f7 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -44,7 +44,7 @@ namespace DetourNavigator }; } - RecastSettings makeRecastSettingsFromSettingsManager(Debug::Level maxLogLevel) + RecastSettings makeRecastSettingsFromSettingsManager() { RecastSettings result; @@ -63,7 +63,6 @@ namespace DetourNavigator result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; result.mTileSize = ::Settings::navigator().mTileSize; - result.mMaxLogLevel = maxLogLevel; return result; } @@ -81,11 +80,11 @@ namespace DetourNavigator } } - Settings makeSettingsFromSettingsManager(Debug::Level maxLogLevel) + Settings makeSettingsFromSettingsManager() { Settings result; - result.mRecast = makeRecastSettingsFromSettingsManager(maxLogLevel); + result.mRecast = makeRecastSettingsFromSettingsManager(); result.mDetour = makeDetourSettingsFromSettingsManager(); const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index ecd9cf073b..1d1f6f5847 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H -#include - #include #include @@ -25,7 +23,6 @@ namespace DetourNavigator int mRegionMergeArea = 0; int mRegionMinArea = 0; int mTileSize = 0; - Debug::Level mMaxLogLevel = Debug::Error; }; struct DetourSettings @@ -58,7 +55,7 @@ namespace DetourNavigator inline constexpr std::int64_t navMeshFormatVersion = 2; - Settings makeSettingsFromSettingsManager(Debug::Level maxLogLevel); + Settings makeSettingsFromSettingsManager(); } #endif diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index f724331548..81cc867aff 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -1,9 +1,8 @@ #include "luascripts.hpp" -#include -#include +#include "components/esm3/esmreader.hpp" +#include "components/esm3/esmwriter.hpp" -#include #include // List of all records, that are related to Lua. @@ -103,16 +102,13 @@ void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm) throw std::runtime_error("Incorrect contentFile index"); }; - LuaUtil::LuaStatePtr state(luaL_newstate()); - if (state == nullptr) - throw std::runtime_error("Failed to create Lua runtime"); - + lua_State* L = luaL_newstate(); LuaUtil::BasicSerializer serializer(adjustRefNumFn); auto adjustLuaData = [&](std::string& data) { if (data.empty()) return; - sol::object luaData = LuaUtil::deserialize(state.get(), data, &serializer); + sol::object luaData = LuaUtil::deserialize(L, data, &serializer); data = LuaUtil::serialize(luaData, &serializer); }; @@ -127,6 +123,7 @@ void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm) refCfg.mRefnumContentFile = adjustRefNumFn(refCfg.mRefnumContentFile); } } + lua_close(L); } void ESM::LuaScriptsCfg::save(ESMWriter& esm) const diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 5a1fa91f28..a71eccfb84 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -32,7 +32,7 @@ namespace ESM void EffectList::updateIndexes() { for (size_t i = 0; i < mList.size(); i++) - mList[i].mIndex = static_cast(i); + mList[i].mIndex = i; } void EffectList::add(ESMReader& esm) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index adb0d1c103..4f69b8edef 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -73,10 +73,10 @@ namespace ESM int index = getIndex(); for (int i = 0; i < getIndex(); i++) { - if (readers.getFileSize(static_cast(i)) == 0) + const ESM::ReadersCache::BusyItem reader = readers.get(static_cast(i)); + if (reader->getFileSize() == 0) continue; // Content file in non-ESM format - const auto fnamecandidate - = Files::pathToUnicodeString(readers.getName(static_cast(i)).filename()); + const auto fnamecandidate = Files::pathToUnicodeString(reader->getName().filename()); if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; diff --git a/components/esm3/readerscache.cpp b/components/esm3/readerscache.cpp index 34ceff5ffd..732a2a22ba 100644 --- a/components/esm3/readerscache.cpp +++ b/components/esm3/readerscache.cpp @@ -47,7 +47,6 @@ namespace ESM { it->mReader.open(*it->mName); it->mName.reset(); - it->mFileSize.reset(); } mBusyItems.splice(mBusyItems.end(), mClosedItems, it); break; @@ -58,46 +57,6 @@ namespace ESM return BusyItem(*this, it); } - const std::filesystem::path& ReadersCache::getName(std::size_t index) const - { - const auto indexIt = mIndex.find(index); - if (indexIt == mIndex.end()) - throw std::logic_error("ESMReader at index " + std::to_string(index) + " has not been created yet"); - switch (indexIt->second->mState) - { - case State::Busy: - case State::Free: - return indexIt->second->mReader.getName(); - case State::Closed: - if (indexIt->second->mName) - return *indexIt->second->mName; - throw std::logic_error("ESMReader at index " + std::to_string(index) + " has forgotten its filename"); - default: - throw std::logic_error("ESMReader at index " + std::to_string(index) + " in unknown state"); - } - } - - std::size_t ReadersCache::getFileSize(std::size_t index) - { - const auto indexIt = mIndex.find(index); - if (indexIt == mIndex.end()) - return 0; - switch (indexIt->second->mState) - { - case State::Busy: - case State::Free: - if (!indexIt->second->mReader.getName().empty()) - return indexIt->second->mReader.getFileSize(); - throw std::logic_error("ESMReader at index " + std::to_string(index) + " has not been opened yet"); - case State::Closed: - if (indexIt->second->mFileSize) - return *indexIt->second->mFileSize; - throw std::logic_error("ESMReader at index " + std::to_string(index) + " has forgotten its file size"); - default: - throw std::logic_error("ESMReader at index " + std::to_string(index) + " in unknown state"); - } - } - void ReadersCache::closeExtraReaders() { while (!mFreeItems.empty() && mBusyItems.size() + mFreeItems.size() + 1 > mCapacity) @@ -106,7 +65,6 @@ namespace ESM if (it->mReader.isOpen()) { it->mName = it->mReader.getName(); - it->mFileSize = it->mReader.getFileSize(); it->mReader.close(); } mClosedItems.splice(mClosedItems.end(), mFreeItems, it); diff --git a/components/esm3/readerscache.hpp b/components/esm3/readerscache.hpp index 3f6ac01874..80ca5d1ef3 100644 --- a/components/esm3/readerscache.hpp +++ b/components/esm3/readerscache.hpp @@ -26,7 +26,6 @@ namespace ESM State mState = State::Busy; ESMReader mReader; std::optional mName; - std::optional mFileSize; Item() = default; }; @@ -56,10 +55,6 @@ namespace ESM BusyItem get(std::size_t index); - const std::filesystem::path& getName(std::size_t index) const; - - std::size_t getFileSize(std::size_t index); - void clear(); private: diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index bdbc94e07a..27c23d92f1 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -114,9 +114,6 @@ void ESM4::Quest::load(ESM4::Reader& reader) case ESM::fourCC("NNAM"): // FO3 case ESM::fourCC("QOBJ"): // FO3 case ESM::fourCC("NAM0"): // FO3 - case ESM::fourCC("SLSD"): // FO3 - case ESM::fourCC("SCVR"): // FO3 - case ESM::fourCC("SCRV"): // FO3 case ESM::fourCC("ANAM"): // TES5 case ESM::fourCC("DNAM"): // TES5 case ESM::fourCC("ENAM"): // TES5 diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index e8be484355..914fa4a647 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -217,7 +217,7 @@ namespace ESM4 // Methods added for updating loading progress bars inline std::size_t getFileSize() const { return mFileSize; } - inline std::size_t getFileOffset() const { return mSavedStream ? mSavedStream->tellg() : mStream->tellg(); } + 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 diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp index 9540c85654..b48dff50d1 100644 --- a/components/fallback/validate.hpp +++ b/components/fallback/validate.hpp @@ -24,6 +24,8 @@ namespace Fallback }; // 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; void validate(boost::any& v, std::vector const& tokens, FallbackMap*, int); } diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 6fb9845976..bbe0325b58 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -36,14 +36,12 @@ namespace Files { std::filesystem::path userPath = std::filesystem::current_path(); - PWSTR cString; - HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &cString); - if (SUCCEEDED(result)) - userPath = std::filesystem::path(cString); - else - Log(Debug::Error) << "Error " << result << " when getting Documents path"; + WCHAR path[MAX_PATH + 1] = {}; - CoTaskMemFree(cString); + if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr, 0, path))) + { + userPath = std::filesystem::path(path); + } return userPath / "My Games" / mName; } @@ -56,19 +54,14 @@ namespace Files std::filesystem::path WindowsPath::getGlobalConfigPath() const { - // The concept of a global config path is absurd on Windows. - // Always use local config instead. - // The virtual base class requires that we provide this, though. std::filesystem::path globalPath = std::filesystem::current_path(); - PWSTR cString; - HRESULT result = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &cString); - if (SUCCEEDED(result)) - globalPath = std::filesystem::path(cString); - else - Log(Debug::Error) << "Error " << result << " when getting Program Files path"; + WCHAR path[MAX_PATH + 1] = {}; - CoTaskMemFree(cString); + if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, nullptr, 0, path))) + { + globalPath = std::filesystem::path(path); + } return globalPath / mName; } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index c9003f3aa8..877d38116b 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -227,10 +227,9 @@ namespace namespace Gui { - FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor, bool exportFonts) + FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor) : mVFS(vfs) , mScalingFactor(scalingFactor) - , mExportFonts(exportFonts) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; @@ -364,8 +363,8 @@ namespace Gui Point bottom_right; float width; float height; - float kerningLeft; - float kerningRight; + float u2; // appears unused, always 0 + float kerning; float ascent; } GlyphInfo; @@ -408,8 +407,7 @@ namespace Gui file.reset(); // Create the font texture - const std::string name(name_); - const std::string bitmapFilename = "fonts/" + name + ".tex"; + std::string bitmapFilename = "fonts/" + std::string(name_) + ".tex"; Files::IStreamPtr bitmapFile = mVFS->get(bitmapFilename); @@ -427,23 +425,9 @@ namespace Gui textureData.resize(width * height * 4); bitmapFile->read(textureData.data(), width * height * 4); if (!bitmapFile->good()) - Log(Debug::Warning) << "Font bitmap " << bitmapFilename << " ended prematurely, using partial data (" - << bitmapFile->gcount() << "/" << (width * height * 4) << " bytes)"; + fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); bitmapFile.reset(); - if (mExportFonts) - { - osg::ref_ptr image = new osg::Image; - image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); - assert(image->isDataContiguous()); - memcpy(image->data(), textureData.data(), textureData.size()); - // Convert to OpenGL origin for sensible output - image->flipVertical(); - - Log(Debug::Info) << "Writing " << name + ".png"; - osgDB::writeImageFile(*image, name + ".png"); - } - MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture(bitmapFilename); tex->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); unsigned char* texData = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); @@ -481,7 +465,7 @@ namespace Gui // € (Euro Sign, 0x80/U+20AC) is replaced with underscore // 0x81 (unused) is replaced with underscore additional.emplace(44, 0x201A); // ‚ (Single Low-9 Quotation Mark, 0x82) => , (comma) - // ƒ (Latin Small Letter F with Hook, 0x83) is unavailable, not replaced + additional.emplace(102, 0x0192); // ƒ (Latin Small Letter F with Hook, 0x83) => f (latin small F) (custom) additional.emplace(44, 0x201E); // „ (Double Low-9 Quotation Mark, 0x84) => , (comma) additional.emplace(46, 0x2026); // … (Horizontal Ellipsis, 0x85) => . (period) additional.emplace(43, 0x2020); // † (Dagger, 0x86) => + (plus sign) @@ -516,7 +500,7 @@ namespace Gui // £ (Pound Sign, 0xA3) is available but its glyph looks like œ (small oe ligature) omitted.push_back(0x00A4); // ¤ (Currency Sign) // ¥ (Yen Sign, 0xA5) is unavailable, not replaced - additional.emplace(221, 0x00A6); // ¦ (Broken Bar, 0xA6) => ▌ + // ¦ (Broken Bar, 0xA6) is unavailable, not replaced omitted.push_back(0x00A7); // § (Section Sign) additional.emplace(34, 0x00A8); // ¨ (Diaeresis) => " (double quote mark) additional.emplace(99, 0x00A9); // © (Copyright Sign) => c (latin small C) @@ -528,7 +512,7 @@ namespace Gui additional.emplace(95, 0x00AF); // ¯ (Macron) => _ (underscore) // ° (Degree Sign, 0xB0) is unavailable, not replaced // ± (Plus-Minus Sign, 0xB1) is unavailable, not replaced - // ² (Superscript Two, 0xB2) is unavailable, not replaced + additional.emplace(50, 0x00B2); // ² (Superscript Two) => 2 (two digit) (custom) additional.emplace(51, 0x00B3); // ³ (Superscript Three) => 3 (three digit) additional.emplace(39, 0x00B4); // ´ (Acute Accent) => ' (apostrophe) // µ (Micro Sign, 0xB5) is unavailable, not replaced @@ -548,7 +532,7 @@ namespace Gui additional.emplace(65, 0x00C3); // à (Latin Capital Letter A with Tilde) => A (latin capital A) // Ä (Latin Capital Letter A with Diaeresis, 0xC4) is available // Å (Latin Capital Letter A with Ring Above, 0xC5) is available - // Æ (Latin Capital Letter Ae, 0xC6) is unavailable, not replaced + additional.emplace(65, 0x00C6); // Æ (Latin Capital Letter Ae) => A (latin capital A) (custom) // Ç (Latin Capital Letter C with Cedilla, 0xC7) is available additional.emplace(69, 0x00C8); // È (Latin Capital Letter E with Grave) => E (latin capital E) // É (Latin Capital Letter E with Acute, 0xC9) is available @@ -559,7 +543,7 @@ namespace Gui additional.emplace(73, 0x00CE); // Î (Latin Capital Letter I with Circumflex) => I (latin capital I) additional.emplace(73, 0x00CF); // Ï (Latin Capital Letter I with Diaeresis) => I (latin capital I) additional.emplace(68, 0x00D0); // Ð (Latin Capital Letter Eth) => D (latin capital D) - // Ñ (Latin Capital Letter N with Tilde, 0xD1) is unavailable, not replaced + additional.emplace(78, 0x00D1); // Ñ (Latin Capital Letter N with Tilde) => N (latin capital N) (custom) additional.emplace(79, 0x00D2); // Ò (Latin Capital Letter O with Grave) => O (latin capital O) additional.emplace(79, 0x00D3); // Ó (Latin Capital Letter O with Acute) => O (latin capital O) additional.emplace(79, 0x00D4); // Ô (Latin Capital Letter O with Circumflex) => O (latin capital O) @@ -572,12 +556,7 @@ namespace Gui additional.emplace(85, 0x00DB); // Û (Latin Capital Letter U with Circumflex) => U (latin capital U) // Ü (Latin Capital Letter U with Diaeresis, 0xDC) is available additional.emplace(89, 0x00DD); // Ý (Latin Capital Letter Y with Acute) => Y (latin capital Y) - // 0xDE to 0xFF are generally not replaced with certain exceptions - additional.emplace(97, 0x00E3); // ã (Latin Small Letter A with Tilde) => a (latin small A) - additional.emplace(100, 0x00F0); // ð (Latin Small Letter Eth) => d (latin small D) - additional.emplace(111, 0x00F5); // õ (Latin Small Letter O with Tilde) => o (latin small O) - additional.emplace(111, 0x00F8); // ø (Latin Small Letter O with Stroke) => o (latin small O) - additional.emplace(121, 0x00FD); // ý (Latin Small Letter Y with Acute) => y (latin small Y) + // 0xDE to 0xFF are not replaced // Russian Morrowind which uses Win-1251 encoding only does equivalent (often garbage) Win-1252 replacements // However, we'll provide custom replacements for Cyrillic io letters @@ -585,12 +564,6 @@ namespace Gui additional.emplace(69, 0x0401); // Ё (Cyrillic Capital Letter Io) => E (latin capital E) additional.emplace(137, 0x0451); // ё (Cyrillic Small Letter Io) => ë (latin small E-diaeresis) - // ASCII vertical bar, use this as text input cursor - additional.emplace(124, MyGUI::FontCodeType::Cursor); - - // Underscore, use for NotDefined marker (used for glyphs not existing in the font) - additional.emplace(95, MyGUI::FontCodeType::NotDefined); - for (int i = 0; i < 256; i++) { float x1 = data[i].top_left.x * width; @@ -600,31 +573,64 @@ namespace Gui ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = getUnicode(i, encoder, mEncoding); - const std::string coord = MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " - + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h); - float advance = data[i].width + data[i].kerningRight; - // Yes MyGUI, we really do want an advance of 0 sometimes, thank you. - if (advance == 0.f && data[i].width != 0.f) - advance = std::numeric_limits::min(); - const std::string bearing = MyGUI::utility::toString(data[i].kerningLeft) + ' ' - + MyGUI::utility::toString((fontSize - data[i].ascent)); - const MyGUI::IntSize size(static_cast(data[i].width), static_cast(data[i].height)); MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); - code->addAttribute("coord", coord); - code->addAttribute("advance", advance); - code->addAttribute("bearing", bearing); - code->addAttribute("size", size); + code->addAttribute("coord", + MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + + " " + MyGUI::utility::toString(h)); + code->addAttribute("advance", data[i].width); + code->addAttribute("bearing", + MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize - data[i].ascent))); + code->addAttribute( + "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); for (auto [it, end] = additional.equal_range(i); it != end; ++it) { code = codes->createChild("Code"); code->addAttribute("index", it->second); - code->addAttribute("coord", coord); - code->addAttribute("advance", advance); - code->addAttribute("bearing", bearing); - code->addAttribute("size", size); + code->addAttribute("coord", + MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); + code->addAttribute("advance", data[i].width); + code->addAttribute("bearing", + MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize - data[i].ascent))); + code->addAttribute( + "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); + } + + // ASCII vertical bar, use this as text input cursor + if (i == 124) + { + MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); + cursorCode->addAttribute("index", MyGUI::FontCodeType::Cursor); + cursorCode->addAttribute("coord", + MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); + cursorCode->addAttribute("advance", data[i].width); + cursorCode->addAttribute("bearing", + MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize - data[i].ascent))); + cursorCode->addAttribute( + "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); + } + + // Underscore, use for NotDefined marker (used for glyphs not existing in the font) + if (i == 95) + { + MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); + cursorCode->addAttribute("index", MyGUI::FontCodeType::NotDefined); + cursorCode->addAttribute("coord", + MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); + cursorCode->addAttribute("advance", data[i].width); + cursorCode->addAttribute("bearing", + MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize - data[i].ascent))); + cursorCode->addAttribute( + "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); } } @@ -633,19 +639,12 @@ namespace Gui omitted.push_back(MyGUI::FontCodeType::SelectedBack); for (const UnicodeIndex index : omitted) { - MyGUI::xml::ElementPtr code = codes->createChild("Code"); - code->addAttribute("index", index); - code->addAttribute("coord", "0 0 0 0"); - code->addAttribute("advance", "0"); - code->addAttribute("bearing", "0 0"); - code->addAttribute("size", "0 0"); - } - - if (mExportFonts) - { - Log(Debug::Info) << "Writing " << name + ".xml"; - xmlDocument.createDeclaration(); - xmlDocument.save(name + ".xml"); + MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); + cursorCode->addAttribute("index", index); + cursorCode->addAttribute("coord", "0 0 0 0"); + cursorCode->addAttribute("advance", "0"); + cursorCode->addAttribute("bearing", "0 0"); + cursorCode->addAttribute("size", "0 0"); } // Register the font with MyGUI diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp index a7269f1a56..7e9220d58d 100644 --- a/components/fontloader/fontloader.hpp +++ b/components/fontloader/fontloader.hpp @@ -25,8 +25,7 @@ namespace Gui class FontLoader { public: - /// @param exportFonts export the converted fonts (Images and XML with glyph metrics) to files? - FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor, bool exportFonts); + FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor); void overrideLineHeight(MyGUI::xml::ElementPtr _node, std::string_view _file, MyGUI::Version _version); @@ -36,7 +35,6 @@ namespace Gui ToUTF8::FromType mEncoding; const VFS::Manager* mVFS; float mScalingFactor; - bool mExportFonts; void loadFonts(); void loadFont(const std::string& fontName, const std::string& fontId); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index c55bee76e3..cf50d20fe2 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -81,7 +81,6 @@ namespace fx #define omw_Position @position #define omw_Texture1D @texture1D #define omw_Texture2D @texture2D -#define omw_Texture2DArray @texture2DArray #define omw_Texture3D @texture3D #define omw_Vertex @vertex #define omw_FragColor @fragColor @@ -155,7 +154,7 @@ mat4 omw_InvProjectionMatrix() float omw_GetDepth(vec2 uv) { #if OMW_MULTIVIEW - float depth = omw_Texture2DArray(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r; + float depth = omw_Texture2D(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r; #else float depth = omw_Texture2D(omw_SamplerDepth, uv).r; #endif @@ -166,19 +165,10 @@ mat4 omw_InvProjectionMatrix() #endif } - vec4 omw_GetDistortion(vec2 uv) - { -#if OMW_MULTIVIEW - return omw_Texture2DArray(omw_SamplerDistortion, vec3(uv, gl_ViewID_OVR)); -#else - return omw_Texture2D(omw_SamplerDistortion, uv); -#endif - } - vec4 omw_GetLastShader(vec2 uv) { #if OMW_MULTIVIEW - return omw_Texture2DArray(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR)); + return omw_Texture2D(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR)); #else return omw_Texture2D(omw_SamplerLastShader, uv); #endif @@ -187,7 +177,7 @@ mat4 omw_InvProjectionMatrix() vec4 omw_GetLastPass(vec2 uv) { #if OMW_MULTIVIEW - return omw_Texture2DArray(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR)); + return omw_Texture2D(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR)); #else return omw_Texture2D(omw_SamplerLastPass, uv); #endif @@ -196,7 +186,7 @@ mat4 omw_InvProjectionMatrix() vec3 omw_GetNormals(vec2 uv) { #if OMW_MULTIVIEW - return omw_Texture2DArray(omw_SamplerNormals, vec3(uv, gl_ViewID_OVR)).rgb * 2.0 - 1.0; + 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 @@ -285,9 +275,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) { "@hdr", technique.getHDR() ? "1" : "0" }, { "@in", mLegacyGLSL ? "varying" : "in" }, { "@out", mLegacyGLSL ? "varying" : "out" }, { "@position", "gl_Position" }, { "@texture1D", mLegacyGLSL ? "texture1D" : "texture" }, - // Note, @texture2DArray must be defined before @texture2D since @texture2D is a perfect prefix of - // texture2DArray - { "@texture2DArray", mLegacyGLSL ? "texture2DArray" : "texture" }, { "@texture2D", mLegacyGLSL ? "texture2D" : "texture" }, { "@texture3D", mLegacyGLSL ? "texture3D" : "texture" }, { "@vertex", mLegacyGLSL ? "gl_Vertex" : "_omw_Vertex" }, diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 5963e274ec..c99d30a1de 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -85,7 +85,7 @@ namespace fx mDescription = {}; mVersion = {}; mGLSLExtensions.clear(); - mGLSLVersion = (mUBO || Stereo::getMultiview()) ? 330 : 120; + mGLSLVersion = mUBO ? 330 : 120; mGLSLProfile.clear(); mDynamic = false; } @@ -109,14 +109,6 @@ namespace fx { clear(); - if (std::ranges::count(mFilePath.value(), '/') > 1) - { - Log(Debug::Error) << "Could not load technique, invalid location '" << mFilePath << "'"; - - mStatus = Status::File_Not_exists; - return false; - } - if (!mVFS.exists(mFilePath)) { Log(Debug::Error) << "Could not load technique, file does not exist '" << mFilePath << "'"; diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp index b0c7fe2e9f..7c7551ba60 100644 --- a/components/lua/inputactions.cpp +++ b/components/lua/inputactions.cpp @@ -239,29 +239,6 @@ namespace LuaUtil } }); } - - void Registry::clear(bool force) - { - std::vector infoToKeep; - if (!force) - { - for (const Info& info : mInfo) - if (info.mPersistent) - infoToKeep.push_back(info); - } - mKeys.clear(); - mIds.clear(); - mInfo.clear(); - mHandlers.clear(); - mBindings.clear(); - mValues.clear(); - mBindingTree.clear(); - if (!force) - { - for (const Info& i : infoToKeep) - insert(i); - } - } } namespace InputTrigger @@ -315,24 +292,5 @@ namespace LuaUtil }), handlers.end()); } - - void Registry::clear(bool force) - { - std::vector infoToKeep; - if (!force) - { - for (const Info& info : mInfo) - if (info.mPersistent) - infoToKeep.push_back(info); - } - mInfo.clear(); - mHandlers.clear(); - mIds.clear(); - if (!force) - { - for (const Info& i : infoToKeep) - insert(i); - } - } } } diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp index 83de556f9f..d05bb71f2c 100644 --- a/components/lua/inputactions.hpp +++ b/components/lua/inputactions.hpp @@ -29,7 +29,6 @@ namespace LuaUtil::InputAction std::string mName; std::string mDescription; sol::main_object mDefaultValue; - bool mPersistent; }; class MultiTree @@ -74,7 +73,16 @@ namespace LuaUtil::InputAction { mHandlers[safeIdByKey(key)].push_back(handler); } - void clear(bool force = false); + void clear() + { + mKeys.clear(); + mIds.clear(); + mInfo.clear(); + mHandlers.clear(); + mBindings.clear(); + mValues.clear(); + mBindingTree.clear(); + } private: using Id = MultiTree::Node; @@ -102,7 +110,6 @@ namespace LuaUtil::InputTrigger std::string mL10n; std::string mName; std::string mDescription; - bool mPersistent; }; class Registry @@ -123,7 +130,12 @@ namespace LuaUtil::InputTrigger void insert(const Info& info); void registerHandler(std::string_view key, const LuaUtil::Callback& callback); void activate(std::string_view key); - void clear(bool force = false); + void clear() + { + mInfo.clear(); + mHandlers.clear(); + mIds.clear(); + } private: using Id = size_t; diff --git a/components/lua/l10n.cpp b/components/lua/l10n.cpp index 15177bea65..542c81009a 100644 --- a/components/lua/l10n.cpp +++ b/components/lua/l10n.cpp @@ -32,8 +32,7 @@ namespace // Argument names const auto str = LuaUtil::cast(key); - argNames.push_back( - icu::UnicodeString::fromUTF8(icu::StringPiece(str.data(), static_cast(str.size())))); + argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(str.data(), str.size()))); } } } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index f959263153..2f5800c521 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -11,7 +11,6 @@ #include #include -#include "luastateptr.hpp" #include "scriptscontainer.hpp" #include "utf8.hpp" @@ -152,37 +151,37 @@ namespace LuaUtil return newPtr; } - LuaStatePtr LuaState::createLuaRuntime(LuaState* luaState) + lua_State* LuaState::createLuaRuntime(LuaState* luaState) { if (sProfilerEnabled) { Log(Debug::Info) << "Initializing LuaUtil::LuaState with profiler"; - LuaStatePtr state(lua_newstate(&trackingAllocator, luaState)); - if (state != nullptr) - return state; - sProfilerEnabled = false; - Log(Debug::Error) << "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler"; + lua_State* L = lua_newstate(&trackingAllocator, luaState); + if (L) + return L; + else + { + sProfilerEnabled = false; + Log(Debug::Error) + << "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler"; + } } Log(Debug::Info) << "Initializing LuaUtil::LuaState without profiler"; - LuaStatePtr state(luaL_newstate()); - if (state == nullptr) - throw std::runtime_error("Failed to create Lua runtime"); - return state; + lua_State* L = luaL_newstate(); + if (!L) + throw std::runtime_error("Can't create Lua runtime"); + return L; } LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings) : mSettings(settings) - , mLuaState([&] { - LuaStatePtr state = createLuaRuntime(this); - sol::set_default_state(state.get()); - return state; - }()) - , mSol(mLuaState.get()) + , mLuaHolder(createLuaRuntime(this)) + , mSol(mLuaHolder.get()) , mConf(conf) , mVFS(vfs) { if (sProfilerEnabled) - lua_sethook(mLuaState.get(), &countHook, LUA_MASKCOUNT, countHookStep); + lua_sethook(mLuaHolder.get(), &countHook, LUA_MASKCOUNT, countHookStep); protectedCall([&](LuaView& view) { auto& sol = view.sol(); @@ -344,8 +343,7 @@ namespace LuaUtil } sol::protected_function_result LuaState::runInNewSandbox(const VFS::Path::Normalized& path, - const std::string& envName, const std::map& packages, - const sol::main_object& hiddenData) + const std::string& envName, const std::map& packages, const sol::object& hiddenData) { // TODO sol::protected_function script = loadScriptAndCache(path); diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index d842478cb1..32c3151c88 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -10,7 +10,6 @@ #include #include "configuration.hpp" -#include "luastateptr.hpp" namespace VFS { @@ -149,8 +148,8 @@ namespace LuaUtil // 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 VFS::Path::Normalized& path, - const std::string& envName = "unnamed", const std::map& packages = {}, - const sol::main_object& hiddenData = sol::nil); + const std::string& envName = "unnamed", const std::map& packages = {}, + const sol::object& hiddenData = sol::nil); void dropScriptCache() { mCompiledScripts.clear(); } @@ -189,7 +188,7 @@ namespace LuaUtil static void countHook(lua_State* L, lua_Debug* ar); static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize); - static LuaStatePtr createLuaRuntime(LuaState* luaState); + lua_State* createLuaRuntime(LuaState* luaState); struct AllocOwner { @@ -207,8 +206,25 @@ namespace LuaUtil uint64_t mSmallAllocMemoryUsage = 0; std::vector mMemoryUsage; + class LuaStateHolder + { + public: + LuaStateHolder(lua_State* L) + : L(L) + { + sol::set_default_state(L); + } + ~LuaStateHolder() { lua_close(L); } + LuaStateHolder(const LuaStateHolder&) = delete; + LuaStateHolder(LuaStateHolder&&) = delete; + lua_State* get() { return L; } + + private: + lua_State* L; + }; + // Must be declared before mSol and all sol-related objects. Then on exit it will be destructed the last. - LuaStatePtr mLuaState; + LuaStateHolder mLuaHolder; sol::state_view mSol; const ScriptsConfiguration* mConf; diff --git a/components/lua/luastateptr.hpp b/components/lua/luastateptr.hpp deleted file mode 100644 index 09733b0754..0000000000 --- a/components/lua/luastateptr.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef OPENMW_COMPONENTS_LUA_LUASTATEPTR_H -#define OPENMW_COMPONENTS_LUA_LUASTATEPTR_H - -#include - -#include - -namespace LuaUtil -{ - struct CloseLuaState - { - void operator()(lua_State* state) noexcept { lua_close(state); } - }; - - using LuaStatePtr = std::unique_ptr; -} - -#endif diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 5eff211894..88f4b9c33c 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -52,7 +52,7 @@ namespace LuaUtil Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what(); } - void ScriptsContainer::addPackage(std::string packageName, sol::main_object package) + void ScriptsContainer::addPackage(std::string packageName, sol::object package) { if (!package.is()) throw std::logic_error("Expected package to be read-only: " + packageName); @@ -312,7 +312,7 @@ namespace LuaUtil { if (next->mOnOverride) { - sol::main_object prevInterface = sol::nil; + sol::object prevInterface = sol::nil; if (prev) prevInterface = *prev->mInterface; try diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 956b4d4317..2059983c1f 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -90,7 +90,7 @@ namespace LuaUtil // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. - void addPackage(std::string packageName, sol::main_object package); + void addPackage(std::string packageName, sol::object package); // 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 @@ -168,7 +168,7 @@ namespace LuaUtil struct Handler { int mScriptId; - sol::main_function mFn; + sol::function mFn; }; struct EngineHandlerList @@ -212,11 +212,11 @@ namespace LuaUtil private: struct Script { - std::optional mOnSave; - std::optional mOnOverride; - std::optional mInterface; + std::optional mOnSave; + std::optional mOnOverride; + std::optional mInterface; std::string mInterfaceName; - sol::main_table mHiddenData; + sol::table mHiddenData; std::map mRegisteredCallbacks; std::map mTemporaryCallbacks; VFS::Path::Normalized mPath; @@ -268,11 +268,11 @@ namespace LuaUtil const UserdataSerializer* mSerializer = nullptr; const UserdataSerializer* mSavedDataDeserializer = nullptr; - std::map mAPI; + std::map mAPI; struct LoadedData { std::map mScripts; - sol::main_table mPublicInterfaces; + sol::table mPublicInterfaces; std::map> mEventHandlers; diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index 9d3d233fb7..9f3a159b3a 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -37,7 +37,7 @@ namespace LuaUtil sol::object LuaStorage::Value::getReadOnly(lua_State* L) const { if (mReadOnlyValue == sol::nil && !mSerializedValue.empty()) - mReadOnlyValue = sol::main_object(deserialize(L, mSerializedValue, nullptr, true)); + mReadOnlyValue = deserialize(L, mSerializedValue, nullptr, true); return mReadOnlyValue; } diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index f86feddb2d..5ca151c30d 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -74,7 +74,7 @@ namespace LuaUtil private: std::string mSerializedValue; - mutable sol::main_object mReadOnlyValue = sol::nil; + mutable sol::object mReadOnlyValue = sol::nil; }; struct Section diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index aff38fca57..1a0379b817 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -18,7 +18,7 @@ namespace LuaUi { public: // accepts only Lua tables returned by ui.content - explicit ContentView(sol::main_table table) + explicit ContentView(sol::table table) : mTable(std::move(table)) { if (!isValidContent(mTable)) @@ -94,7 +94,7 @@ namespace LuaUi sol::table getMetatable() const { return mTable[sol::metatable_key].get(); } private: - sol::main_table mTable; + sol::table mTable; template sol::object callMethod(std::string_view name, Arg&&... arg) const diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ce6d57e154..2d8462e273 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -324,7 +324,7 @@ namespace LuaUi destroyRoot(mRoot); mRoot = nullptr; } - mLayout.reset(); + mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } mState = Destroyed; } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index ffd15ac950..39a1fdd769 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -19,7 +19,7 @@ namespace LuaUi } WidgetExtension* mRoot; - sol::main_object mLayout; + sol::object mLayout; std::string mLayer; enum State diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index acbdbcd03a..71416be8c8 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -281,7 +281,7 @@ namespace LuaUi updateChildrenCoord(); } - void WidgetExtension::setProperties(const sol::main_object& props) + void WidgetExtension::setProperties(const sol::object& props) { mProperties = props; updateProperties(); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 5fcf86d110..0ec688d3bb 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -50,10 +50,10 @@ namespace LuaUi void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); - void setProperties(const sol::main_object& props); - void setTemplateProperties(const sol::main_object& props) { mTemplateProperties = props; } + void setProperties(const sol::object& props); + void setTemplateProperties(const sol::object& props) { mTemplateProperties = props; } - void setExternal(const sol::main_object& external) { mExternal = external; } + void setExternal(const sol::object& external) { mExternal = external; } MyGUI::IntCoord forcedCoord(); void forceCoord(const MyGUI::IntCoord& offset); @@ -63,7 +63,7 @@ namespace LuaUi virtual void updateCoord(); - const sol::main_table& getLayout() { return mLayout; } + const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } template @@ -150,10 +150,10 @@ namespace LuaUi std::vector mTemplateChildren; WidgetExtension* mSlot; std::map> mCallbacks; - sol::main_table mLayout; - sol::main_object mProperties; - sol::main_object mTemplateProperties; - sol::main_object mExternal; + sol::table mLayout; + sol::object mProperties; + sol::object mTemplateProperties; + sol::object mExternal; WidgetExtension* mParent; bool mTemplateChild; bool mElementRoot; diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index ef63bae937..f960e8d972 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -228,17 +228,13 @@ namespace Nif { if (getVersion() < generateVersion(4, 1, 0, 0)) { - std::vector buf(size); - read(buf.data(), size); - for (size_t i = 0; i < size; ++i) - dest[i] = buf[i] != 0; + for (bool& value : std::span(dest, size)) + value = get() != 0; } else { - std::vector buf(size); - read(buf.data(), size); - for (size_t i = 0; i < size; ++i) - dest[i] = buf[i] != 0; + for (bool& value : std::span(dest, size)) + value = get() != 0; } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 07eb342221..d817ed2c9f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -391,7 +391,6 @@ namespace NifOsg osg::ref_ptr skel = new SceneUtil::Skeleton; skel->setStateSet(created->getStateSet()); skel->setName(created->getName()); - skel->setUserDataContainer(created->getUserDataContainer()); for (unsigned int i = 0; i < created->getNumChildren(); ++i) skel->addChild(created->getChild(i)); created->removeChildren(0, created->getNumChildren()); diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp index c285988f40..c812feb07e 100644 --- a/components/sceneutil/animblendrules.cpp +++ b/components/sceneutil/animblendrules.cpp @@ -1,13 +1,17 @@ #include "animblendrules.hpp" #include -#include +#include #include #include #include #include +#include +#include +#include +#include #include #include diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 90dc8237f2..08992b1ae3 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -119,8 +119,7 @@ namespace SceneUtil osg::ref_ptr light(new osg::Light); lightSource->setNodeMask(lightMask); - // The minimum scene light radius is 16 in Morrowind - const float radius = std::max(esmLight.mRadius, 16.f); + float radius = esmLight.mRadius; lightSource->setRadius(radius); configureLight(light, radius, isExterior); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 9fa01385d5..9fd435f40d 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -986,12 +986,9 @@ void SceneUtil::MWShadowTechnique::copyShadowMap(osgUtil::CullVisitor& cv, ViewD lhs_sd->_camera = rhs_sd->_camera; lhs_sd->_textureUnit = rhs_sd->_textureUnit; lhs_sd->_texture = rhs_sd->_texture; - lhs_sd->_sm_i = rhs_sd->_sm_i; sdl.push_back(lhs_sd); } - copyShadowStateSettings(cv, lhs); - if (lhs->_numValidShadows > 0) { prepareStateSetForRenderingShadow(*lhs, cv.getTraversalNumber()); @@ -1003,14 +1000,6 @@ void SceneUtil::MWShadowTechnique::setCustomFrustumCallback(CustomFrustumCallbac _customFrustumCallback = cfc; } -void SceneUtil::MWShadowTechnique::copyShadowStateSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd) -{ - for (const auto& sd : vdd->getShadowDataList()) - { - assignValidRegionSettings(cv, sd->_camera, sd->_sm_i, vdd->_uniforms[cv.getTraversalNumber()%2]); - assignShadowStateSettings(cv, sd->_camera, sd->_sm_i, vdd->_uniforms[cv.getTraversalNumber()%2]); - } -} void MWShadowTechnique::update(osg::NodeVisitor& nv) { @@ -1064,8 +1053,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) _shadowedScene->osg::Group::traverse(cv); return; } - - Uniforms& vddUniforms = vdd->_uniforms[cv.getTraversalNumber() % 2]; ShadowSettings* settings = getShadowedScene()->getShadowSettings(); @@ -1513,7 +1500,29 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (!orthographicViewFrustum && settings->getShadowMapProjectionHint()==ShadowSettings::PERSPECTIVE_SHADOW_MAP) { - assignValidRegionSettings(cv, camera, sm_i, vddUniforms); + { + osg::Matrix validRegionMatrix = cv.getCurrentCamera()->getInverseViewMatrix() * camera->getViewMatrix() * camera->getProjectionMatrix(); + + std::string validRegionUniformName = "validRegionMatrix" + std::to_string(sm_i); + osg::ref_ptr validRegionUniform; + + for (const auto & uniform : _uniforms[cv.getTraversalNumber() % 2]) + { + if (uniform->getName() == validRegionUniformName) + { + validRegionUniform = uniform; + break; + } + } + + if (!validRegionUniform) + { + validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); + _uniforms[cv.getTraversalNumber() % 2].push_back(validRegionUniform); + } + + validRegionUniform->set(validRegionMatrix); + } if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar); @@ -1528,7 +1537,31 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) // 4.4 compute main scene graph TexGen + uniform settings + setup state // { - assignShadowStateSettings(cv, camera, sm_i, vddUniforms); + osg::Matrix shadowSpaceMatrix = cv.getCurrentCamera()->getInverseViewMatrix() * + camera->getViewMatrix() * + camera->getProjectionMatrix() * + osg::Matrix::translate(1.0,1.0,1.0) * + osg::Matrix::scale(0.5,0.5,0.5); + + std::string shadowSpaceUniformName = "shadowSpaceMatrix" + std::to_string(sm_i); + osg::ref_ptr shadowSpaceUniform; + + for (const auto & uniform : _uniforms[cv.getTraversalNumber() % 2]) + { + if (uniform->getName() == shadowSpaceUniformName) + { + shadowSpaceUniform = uniform; + break; + } + } + + if (!shadowSpaceUniform) + { + shadowSpaceUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, shadowSpaceUniformName); + _uniforms[cv.getTraversalNumber() % 2].push_back(shadowSpaceUniform); + } + + shadowSpaceUniform->set(shadowSpaceMatrix); } // mark the light as one that has active shadows and requires shaders @@ -1536,7 +1569,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) // pass on shadow data to ShadowDataList sd->_textureUnit = textureUnit; - sd->_sm_i = sm_i; sdl.push_back(sd); @@ -3048,61 +3080,6 @@ bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::Render return true; } -void MWShadowTechnique::assignShadowStateSettings(osgUtil::CullVisitor& cv, osg::Camera* camera, unsigned int sm_i, Uniforms& uniforms) -{ - osg::Matrix inverseViewMatrix = osg::Matrix::inverse(*cv.getModelViewMatrix()); - osg::Matrix shadowSpaceMatrix = inverseViewMatrix * - camera->getViewMatrix() * - camera->getProjectionMatrix() * - osg::Matrix::translate(1.0,1.0,1.0) * - osg::Matrix::scale(0.5,0.5,0.5); - - std::string shadowSpaceUniformName = "shadowSpaceMatrix" + std::to_string(sm_i); - osg::ref_ptr shadowSpaceUniform; - - for (const auto & uniform : uniforms) - { - if (uniform->getName() == shadowSpaceUniformName) - { - shadowSpaceUniform = uniform; - break; - } - } - - if (!shadowSpaceUniform) - { - shadowSpaceUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, shadowSpaceUniformName); - uniforms.push_back(shadowSpaceUniform); - } - - shadowSpaceUniform->set(shadowSpaceMatrix); -} - -void SceneUtil::MWShadowTechnique::assignValidRegionSettings(osgUtil::CullVisitor & cv, osg::Camera* camera, unsigned int sm_i, Uniforms & uniforms) -{ - osg::Matrix validRegionMatrix = osg::Matrix::inverse(*cv.getModelViewMatrix()) * camera->getViewMatrix() * camera->getProjectionMatrix(); - - std::string validRegionUniformName = "validRegionMatrix" + std::to_string(sm_i); - osg::ref_ptr validRegionUniform; - - for (const auto & uniform : uniforms) - { - if (uniform->getName() == validRegionUniformName) - { - validRegionUniform = uniform; - break; - } - } - - if (!validRegionUniform) - { - validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); - uniforms.push_back(validRegionUniform); - } - - validRegionUniform->set(validRegionMatrix); -} - void MWShadowTechnique::cullShadowReceivingScene(osgUtil::CullVisitor* cv) const { OSG_INFO<<"cullShadowReceivingScene()"<addUniform(uniform); } - for(const auto& uniform : vdd._uniforms[traversalNumber % 2]) - { - OSG_INFO<<"addUniform("<getName()<<")"<addUniform(uniform); - } - if (_program.valid()) { stateset->setAttribute(_program.get()); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 4b9de9364a..8b39818e2e 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -157,7 +157,6 @@ namespace SceneUtil { * the resulting shadow map may be invalid. */ virtual void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) = 0; }; - typedef std::vector< osg::ref_ptr > Uniforms; // forward declare class ViewDependentData; @@ -193,7 +192,6 @@ namespace SceneUtil { ViewDependentData* _viewDependentData; unsigned int _textureUnit; - unsigned int _sm_i; osg::ref_ptr _texture; osg::ref_ptr _camera; }; @@ -230,7 +228,6 @@ namespace SceneUtil { LightDataList _lightDataList; ShadowDataList _shadowDataList; - std::array _uniforms; unsigned int _numValidShadows; }; @@ -243,8 +240,6 @@ namespace SceneUtil { void setCustomFrustumCallback(CustomFrustumCallback* cfc); - void copyShadowStateSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd); - virtual void createShaders(); virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; @@ -256,10 +251,6 @@ namespace SceneUtil { virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector& planeList); virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar); - - virtual void assignShadowStateSettings(osgUtil::CullVisitor& cv, osg::Camera* camera, unsigned int sm_i, Uniforms& uniforms); - - virtual void assignValidRegionSettings(osgUtil::CullVisitor& cv, osg::Camera* camera, unsigned int sm_i, Uniforms& uniforms); virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; @@ -289,6 +280,7 @@ namespace SceneUtil { osg::ref_ptr _fallbackBaseTexture; osg::ref_ptr _fallbackShadowMapTexture; + typedef std::vector< osg::ref_ptr > Uniforms; std::array _uniforms; osg::ref_ptr _program; diff --git a/components/sceneutil/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp index 121ef068d8..b4de250e77 100644 --- a/components/sceneutil/pathgridutil.cpp +++ b/components/sceneutil/pathgridutil.cpp @@ -126,9 +126,9 @@ namespace SceneUtil gridGeometry->setVertexArray(vertices); gridGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - if (!pointIndices->empty()) + if (pointIndexCount) gridGeometry->addPrimitiveSet(pointIndices); - if (!lineIndices->empty()) + if (edgeIndexCount) gridGeometry->addPrimitiveSet(lineIndices); gridGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index ac19b8f446..81053aa476 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -208,7 +208,6 @@ namespace SceneUtil "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", - "NifOsg::BillboardCallback", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 41a4f13818..43de84bb70 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -76,18 +76,6 @@ namespace SDLUtil while (SDL_PollEvent(&evt)) { -#if SDL_VERSION_ATLEAST(2, 30, 50) - // SDL2-compat may pass us SDL3 display and window events alongside the SDL2 events for funsies - // Until we are ready to move to SDL3, we'll want to prevent the noise - - // Silence 0x151 to 0x1FF range - if (evt.type > SDL_DISPLAYEVENT && evt.type < SDL_WINDOWEVENT) - continue; - - // Silence 0x202 to 0x2FF range - if (evt.type > SDL_SYSWMEVENT && evt.type < SDL_KEYDOWN) - continue; -#endif switch (evt.type) { case SDL_MOUSEMOTION: diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp index 56740c125a..35e3adf95a 100644 --- a/components/stereo/frustum.hpp +++ b/components/stereo/frustum.hpp @@ -2,7 +2,10 @@ #define STEREO_FRUSTUM_H #include +#include #include +#include +#include #include #include @@ -10,7 +13,6 @@ namespace osg { - class Camera; class FrameBufferObject; class Texture2D; class Texture2DMultisample; @@ -22,7 +24,7 @@ namespace osgViewer class Viewer; } -namespace osgUtil +namespace usgUtil { class CullVisitor; } diff --git a/components/testing/util.hpp b/components/testing/util.hpp index 53331d6d37..941f495458 100644 --- a/components/testing/util.hpp +++ b/components/testing/util.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_COMPONENTS_TESTING_UTIL_H #define OPENMW_COMPONENTS_TESTING_UTIL_H -#include #include #include #include @@ -15,37 +14,26 @@ namespace TestingOpenMW { - inline std::filesystem::path outputDir() + inline std::filesystem::path outputFilePath(const std::string name) { - static const std::string run - = std::to_string(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); - std::filesystem::path dir = std::filesystem::temp_directory_path() / "openmw" / "tests" / run; - std::filesystem::create_directories(dir); - return dir; - } - - inline std::filesystem::path outputFilePath(std::string_view name) - { - std::filesystem::path dir = outputDir(); + std::filesystem::path dir("tests_output"); + std::filesystem::create_directory(dir); return dir / Misc::StringUtils::stringToU8String(name); } - inline std::filesystem::path outputDirPath(const std::filesystem::path& subpath) - { - std::filesystem::path path = outputDir(); - path /= subpath; - std::filesystem::create_directories(path); - return path; - } - inline std::filesystem::path outputFilePathWithSubDir(const std::filesystem::path& subpath) { - std::filesystem::path path = outputDir(); + std::filesystem::path path("tests_output"); path /= subpath; std::filesystem::create_directories(path.parent_path()); return path; } + inline std::filesystem::path temporaryFilePath(const std::string name) + { + return std::filesystem::temp_directory_path() / name; + } + class VFSTestFile : public VFS::File { public: diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 7e34147b1c..15fb8b26c0 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -52,7 +52,7 @@ namespace return std::find_if(input.begin(), input.end(), [](unsigned char v) { return v == 0 || v >= 128; }); } - std::span getTranslationArray(FromType sourceEncoding) + std::basic_string_view getTranslationArray(FromType sourceEncoding) { switch (sourceEncoding) { diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 0dde0fced6..80af6586c9 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -2,7 +2,6 @@ #define COMPONENTS_TOUTF8_H #include -#include #include #include #include @@ -51,7 +50,7 @@ namespace ToUTF8 inline void copyFromArrayLegacyEnc( std::string_view::iterator& chp, std::string_view::iterator end, char*& out) const; - const std::span mTranslationArray; + const std::basic_string_view mTranslationArray; }; class Utf8Encoder diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 42d4eb8d78..8a7a594691 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -150,7 +150,7 @@ 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/``. The ~/Library folder is hidden by default. To get to it from the Installation Wizard popup, you will need to go to Users/YOUR_USERNAME_HERE/ and do ``CMD+SHIFT+PERIOD`` to reveal it. + ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` Linux ^^^^^ diff --git a/docs/source/manuals/installation/install-openmw.rst b/docs/source/manuals/installation/install-openmw.rst index 2ef72abfd5..50efe7542d 100644 --- a/docs/source/manuals/installation/install-openmw.rst +++ b/docs/source/manuals/installation/install-openmw.rst @@ -28,8 +28,13 @@ A `Launchpad PPA `_ is available. Add it and install OpenMW:: $ sudo add-apt-repository ppa:openmw/openmw - $ sudo apt update - $ sudo apt install openmw + $ sudo apt-get update + $ sudo apt-get install openmw openmw-launcher + +.. note:: + OpenMW-CS must be installed separately by typing:: + + $ sudo apt-get install openmw-cs The Arch Linux Way ================== diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 944a483dda..3a074c1ad7 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -21,7 +21,7 @@ - by player scripts - | Allows to alter behavior of the built-in script | that handles player controls. - * - :ref:`GamepadControls ` + * - :ref:`Controls ` - by player scripts - | Allows to alter behavior of the built-in script | that handles player gamepad controls. diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 2c67a940d1..ae2a457e44 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -15,8 +15,6 @@ Morrowind .fnt fonts Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format. To our knowledge, the format is undocumented. OpenMW can load this format and convert it on the fly into something usable (see font loader `source code `_). -You can use --export-fonts command line option to write the converted font -(a PNG image and an XML file describing the position of each glyph in the image) to the current directory. They can be used instead of TrueType fonts if needed by specifying their ``.fnt`` files names in the ``openmw.cfg``. For example: diff --git a/extern/.clang-tidy b/extern/.clang-tidy deleted file mode 100644 index bc766e0a9e..0000000000 --- a/extern/.clang-tidy +++ /dev/null @@ -1,3 +0,0 @@ -Checks: >- - -clang-analyzer-core.NullDereference, - -clang-analyzer-cplusplus.NewDelete diff --git a/extern/sol3/README.md b/extern/sol3/README.md index 202b2ca08b..1674dcf599 100644 --- a/extern/sol3/README.md +++ b/extern/sol3/README.md @@ -1,5 +1,3 @@ The code in this directory is copied from https://github.com/ThePhD/sol2.git (64096348465b980e2f1d0e5ba9cbeea8782e8f27) -Additional changes include cherry-picking upstream commit d805d027e0a0a7222e936926139f06e23828ce9f to fix compilation under Clang 19. - License: MIT diff --git a/extern/sol3/sol/optional_implementation.hpp b/extern/sol3/sol/optional_implementation.hpp index a35df4ec87..b7673b17b1 100644 --- a/extern/sol3/sol/optional_implementation.hpp +++ b/extern/sol3/sol/optional_implementation.hpp @@ -2191,8 +2191,7 @@ namespace sol { static_assert(std::is_constructible::value, "T must be constructible with Args"); *this = nullopt; - new (static_cast(this)) optional(std::in_place, std::forward(args)...); - return **this; + this->construct(std::forward(args)...); } /// Swaps this optional with the other. diff --git a/files/data/fonts/MysticCards.omwfont b/files/data/fonts/MysticCards.omwfont index b0dccb1445..3458614ddc 100644 --- a/files/data/fonts/MysticCards.omwfont +++ b/files/data/fonts/MysticCards.omwfont @@ -2,13 +2,13 @@ - + - + diff --git a/files/data/fonts/MysticCards.ttf b/files/data/fonts/MysticCards.ttf index c98597d749..1b93f5aadc 100644 Binary files a/files/data/fonts/MysticCards.ttf and b/files/data/fonts/MysticCards.ttf differ diff --git a/files/data/scripts/omw/crimes.lua b/files/data/scripts/omw/crimes.lua index 8539471973..58e043964b 100644 --- a/files/data/scripts/omw/crimes.lua +++ b/files/data/scripts/omw/crimes.lua @@ -24,7 +24,7 @@ return { interface = { --- Interface version -- @field [parent=#Crimes] #number version - version = 2, + version = 1, --- -- Commits a crime as if done through an in-game action. Can only be used in global context. @@ -42,7 +42,7 @@ return { "faction id passed to commitCrime must be a string or nil") assert(type(options.arg) == "number" or options.arg == nil, "arg value passed to commitCrime must be a number or nil") - assert(type(options.victimAware) == "boolean" or options.victimAware == nil, + assert(type(options.victimAware) == "number" or options.victimAware == nil, "victimAware value passed to commitCrime must be a boolean or nil") assert(options.type ~= nil, "crime type passed to commitCrime cannot be nil") diff --git a/files/data/scripts/omw/input/gamepadcontrols.lua b/files/data/scripts/omw/input/gamepadcontrols.lua index 1f944fc708..b55b806b4a 100644 --- a/files/data/scripts/omw/input/gamepadcontrols.lua +++ b/files/data/scripts/omw/input/gamepadcontrols.lua @@ -6,7 +6,7 @@ return { --- -- Gamepad control interface -- @module GamepadControls - -- @usage require('openmw.interfaces').GamepadControls + interface = { --- Interface version -- @field [parent=#GamepadControls] #number version diff --git a/files/data/scripts/omw/input/playercontrols.lua b/files/data/scripts/omw/input/playercontrols.lua index 244b952a1a..32a6f404c1 100644 --- a/files/data/scripts/omw/input/playercontrols.lua +++ b/files/data/scripts/omw/input/playercontrols.lua @@ -89,7 +89,7 @@ local function processMovement() local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') local run = input.getBooleanActionValue('Run') ~= settings:get('alwaysRun') - if movement ~= 0 then + if movement ~= 0 or not Actor.canMove(self) then autoMove = false elseif autoMove then movement = 1 diff --git a/files/data/shaders/internal_distortion.omwfx b/files/data/shaders/internal_distortion.omwfx index b8e62ff229..b641bb6711 100644 --- a/files/data/shaders/internal_distortion.omwfx +++ b/files/data/shaders/internal_distortion.omwfx @@ -6,11 +6,11 @@ fragment main { { const float multiplier = 0.14; - vec2 offset = omw_GetDistortion(omw_TexCoord).rg; + vec2 offset = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord).rg; offset *= multiplier; offset = clamp(offset, vec2(-1.0), vec2(1.0)); - float occlusionFactor = omw_GetDistortion(omw_TexCoord+offset).b; + float occlusionFactor = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord+offset).b; omw_FragColor = mix(omw_GetLastShader(omw_TexCoord + offset), omw_GetLastShader(omw_TexCoord), occlusionFactor); } diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index d15241afff..46f3ff20fc 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -221,14 +221,14 @@ -- Can be used only in local scripts on self. Can also be evoked by sending an AddVfx event to the target actor. -- @function [parent=#animation] addVfx -- @param openmw.core#GameObject actor --- @param #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar) +-- @param #string model #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar) -- @param #table options optional table of parameters. Can contain: -- --- * `loop` - boolean, if true the effect will loop until removed (default: false). +-- * `loop` - boolean, if true the effect will loop until removed (default: 0). -- * `boneName` - name of the bone to attach the vfx to. (default: "") -- * `particleTextureOverride` - name of the particle texture to use. (default: "") -- * `vfxId` - a string ID that can be used to remove the effect later, using #removeVfx, and to avoid duplicate effects. The default value of "" can have duplicates. To avoid interaction with the engine, use unique identifiers unrelated to magic effect IDs. The engine uses this identifier to add and remove magic effects based on what effects are active on the actor. If this is set equal to the @{openmw.core#MagicEffectId} identifier of the magic effect being added, for example core.magic.EFFECT_TYPE.FireDamage, then the engine will remove it once the fire damage effect on the actor reaches 0. (Default: ""). --- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: true) +-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: 1) -- -- @usage local mgef = core.magic.effects.records[myEffectName] -- anim.addVfx(self, 'VFX_Hands', {boneName = 'Bip01 L Hand', particleTextureOverride = mgef.particle, loop = mgef.continuousVfx, vfxId = mgef.id..'_myuniquenamehere'}) diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index f2a3dbbc37..ea1b8738bc 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -59,7 +59,7 @@ -- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE --- --- Result of raycasting +-- 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) @@ -69,7 +69,7 @@ --- -- A table of parameters for @{#nearby.castRay} -- @type CastRayOptions --- @field #any ignore An @{openmw.core#GameObject} or @{openmw.core#ObjectList} to ignore (specify here the source of the ray, or other objects which should not collide) +-- @field openmw.core#GameObject ignore An object to ignore (specify here the source of the ray) -- @field #number collisionType Object types to work with (see @{openmw.nearby#COLLISION_TYPE}) -- @field #number 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`. @@ -92,7 +92,7 @@ --- -- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay} -- @type CastRenderingRayOptions --- @field #any ignore A @{openmw.core#GameObject} or @{openmw.core#ObjectList} to ignore while doing the ray cast +-- @field #table ignore A list of @{openmw.core#GameObject} to ignore while doing the ray cast --- -- Cast ray from one point to another and find the first visual intersection with anything in the scene. @@ -102,7 +102,7 @@ -- @function [parent=#nearby] castRenderingRay -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. --- @param #CastRenderingRayOptions options An optional table with additional optional arguments +-- @param #CastRenderingRayOptions -- @return #RayCastingResult --- diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 3a45bd68d8..4da5fa3ee9 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -45,7 +45,7 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @{#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead. +-- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead. --- -- Enables or disables standard AI (enabled by default). diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 0012a51c41..e1b814f8b3 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1123,7 +1123,7 @@ -- @field #string id The record ID of the NPC -- @field #string name -- @field #string race --- @field #string class ID of the NPC's class (e.g. acrobat) +-- @field #string class Name of the NPC's class (e. g. Acrobat) -- @field #string model Path to the model associated with this NPC, used for animations. -- @field #string mwscript MWScript on this NPC (can be nil) -- @field #string hair Path to the hair body part model diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 6f38c4df5e..5068c01d80 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -26,6 +26,8 @@ uniform sampler2D normalMap; varying vec2 normalMapUV; #endif +uniform sampler2D opaqueDepthTex; + varying float euclideanDepth; varying float linearDepth; @@ -40,7 +42,6 @@ uniform float specStrength; uniform bool useTreeAnim; uniform float distortionStrength; -#include "lib/core/fragment.h.glsl" #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" #include "lib/util/distortion.glsl" @@ -58,7 +59,7 @@ void main() #if defined(DISTORTION) && DISTORTION vec2 screenCoords = gl_FragCoord.xy / (screenRes * @distorionRTRatio); gl_FragData[0].a *= getDiffuseColor().a; - gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, sampleOpaqueDepthTex(screenCoords).x); + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords).x); return; #endif diff --git a/files/shaders/compatibility/bs/nolighting.frag b/files/shaders/compatibility/bs/nolighting.frag index a46e99aa82..c9e3ca4e13 100644 --- a/files/shaders/compatibility/bs/nolighting.frag +++ b/files/shaders/compatibility/bs/nolighting.frag @@ -26,7 +26,6 @@ uniform float far; uniform float near; uniform float alphaRef; -#include "lib/core/fragment.h.glsl" #include "lib/material/alpha.glsl" #include "compatibility/vertexcolors.glsl" @@ -36,6 +35,7 @@ uniform float alphaRef; #if @softParticles #include "lib/particle/soft.glsl" +uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; uniform float softFalloffDepth; @@ -70,7 +70,7 @@ void main() viewNormal, near, far, - sampleOpaqueDepthTex(screenCoords).x, + texture2D(opaqueDepthTex, screenCoords).x, particleSize, particleFade, softFalloffDepth diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index ad6d262f6d..8bd9664b41 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -89,7 +89,6 @@ varying vec4 passTangent; #define ADDITIVE_BLENDING #endif -#include "lib/core/fragment.h.glsl" #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "lib/material/alpha.glsl" @@ -114,6 +113,8 @@ uniform sampler2D orthoDepthMap; varying vec3 orthoDepthMapCoord; #endif +uniform sampler2D opaqueDepthTex; + void main() { #if @particleOcclusion @@ -142,7 +143,7 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; #if defined(DISTORTION) && DISTORTION gl_FragData[0].a *= getDiffuseColor().a; - gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, sampleOpaqueDepthTex(screenCoords / @distorionRTRatio).x); + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords / @distorionRTRatio).x); return; #endif @@ -257,7 +258,7 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; viewNormal, near, far, - sampleOpaqueDepthTex(screenCoords).x, + texture2D(opaqueDepthTex, screenCoords).x, particleSize, particleFade, softFalloffDepth diff --git a/files/shaders/lib/core/fragment.glsl b/files/shaders/lib/core/fragment.glsl index d58e17feaf..9b983148cb 100644 --- a/files/shaders/lib/core/fragment.glsl +++ b/files/shaders/lib/core/fragment.glsl @@ -40,10 +40,3 @@ vec3 sampleSkyColor(vec2 uv) return texture2D(sky, uv).xyz; } #endif - -uniform sampler2D opaqueDepthTex; - -vec4 sampleOpaqueDepthTex(vec2 uv) -{ - return texture2D(opaqueDepthTex, uv); -} diff --git a/files/shaders/lib/core/fragment.h.glsl b/files/shaders/lib/core/fragment.h.glsl index 9b7ef768e6..b8c3f9a32b 100644 --- a/files/shaders/lib/core/fragment.h.glsl +++ b/files/shaders/lib/core/fragment.h.glsl @@ -17,6 +17,4 @@ vec4 samplerLastShader(vec2 uv); vec3 sampleSkyColor(vec2 uv); #endif -vec4 sampleOpaqueDepthTex(vec2 uv); - #endif // OPENMW_FRAGMENT_H_GLSL diff --git a/files/shaders/lib/core/fragment_multiview.glsl b/files/shaders/lib/core/fragment_multiview.glsl index e29c5afbc9..2880087104 100644 --- a/files/shaders/lib/core/fragment_multiview.glsl +++ b/files/shaders/lib/core/fragment_multiview.glsl @@ -2,7 +2,6 @@ #extension GL_OVR_multiview : require #extension GL_OVR_multiview2 : require -#extension GL_EXT_texture_array : require #include "lib/core/fragment.h.glsl" @@ -45,10 +44,3 @@ vec3 sampleSkyColor(vec2 uv) return texture(sky, vec3((uv), gl_ViewID_OVR)).xyz; } #endif - -uniform sampler2DArray opaqueDepthTex; - -vec4 sampleOpaqueDepthTex(vec2 uv) -{ - return texture(opaqueDepthTex, vec3((uv), gl_ViewID_OVR)); -} diff --git a/scripts/data/integration_tests/test_lua_api/menu.lua b/scripts/data/integration_tests/test_lua_api/menu.lua deleted file mode 100644 index 8c6895a5d8..0000000000 --- a/scripts/data/integration_tests/test_lua_api/menu.lua +++ /dev/null @@ -1,94 +0,0 @@ -local testing = require('testing_util') -local matchers = require('matchers') -local menu = require('openmw.menu') - -testing.registerMenuTest('save and load', function() - menu.newGame() - coroutine.yield() - menu.saveGame('save and load') - coroutine.yield() - - local directorySaves = {} - directorySaves['save_and_load.omwsave'] = { - playerName = '', - playerLevel = 1, - timePlayed = 0, - description = 'save and load', - contentFiles = { - 'builtin.omwscripts', - 'template.omwgame', - 'landracer.omwaddon', - 'the_hub.omwaddon', - 'test_lua_api.omwscripts', - }, - creationTime = matchers.isAny(), - } - local expectedAllSaves = {} - expectedAllSaves[' - 1'] = directorySaves - - testing.expectThat(menu.getAllSaves(), matchers.equalTo(expectedAllSaves)) - - menu.loadGame(' - 1', 'save_and_load.omwsave') - coroutine.yield() - - menu.deleteGame(' - 1', 'save_and_load.omwsave') - testing.expectThat(menu.getAllSaves(), matchers.equalTo({})) -end) - -testing.registerMenuTest('load while teleporting', function() - menu.newGame() - coroutine.yield() - - testing.runGlobalTest('load while teleporting - init player') - - menu.saveGame('load while teleporting') - coroutine.yield() - - testing.runGlobalTest('load while teleporting - teleport') - - menu.loadGame(' - 1', 'load_while_teleporting.omwsave') - coroutine.yield() - - menu.deleteGame(' - 1', 'load_while_teleporting.omwsave') -end) - -local function registerGlobalTest(name, description) - testing.registerMenuTest(description or name, function() - menu.newGame() - coroutine.yield() - testing.runGlobalTest(name) - end) -end - -registerGlobalTest('timers') -registerGlobalTest('teleport') -registerGlobalTest('getGMST') -registerGlobalTest('MWScript') -registerGlobalTest('record stores') -registerGlobalTest('record creation') -registerGlobalTest('UTF-8 characters') -registerGlobalTest('UTF-8 strings') -registerGlobalTest('memory limit') -registerGlobalTest('vfs') -registerGlobalTest('commit crime') -registerGlobalTest('record model property') - -registerGlobalTest('player yaw rotation', 'rotating player with controls.yawChange should change rotation') -registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation') -registerGlobalTest('player pitch and yaw rotation', 'rotating player with controls.pitchChange and controls.yawChange should change rotation') -registerGlobalTest('player rotation', 'rotating player should not lead to nan rotation') -registerGlobalTest('player forward running') -registerGlobalTest('player diagonal walking') -registerGlobalTest('findPath') -registerGlobalTest('findRandomPointAroundCircle') -registerGlobalTest('castNavigationRay') -registerGlobalTest('findNearestNavMeshPosition') -registerGlobalTest('player memory limit') -registerGlobalTest('player weapon attack', 'player with equipped weapon on attack should damage health of other actors') - -return { - engineHandlers = { - onFrame = testing.makeUpdateMenu(), - }, - eventHandlers = testing.menuEventHandlers, -} diff --git a/scripts/data/integration_tests/test_lua_api/openmw.cfg b/scripts/data/integration_tests/test_lua_api/openmw.cfg index efd1cf331c..68e50644a0 100644 --- a/scripts/data/integration_tests/test_lua_api/openmw.cfg +++ b/scripts/data/integration_tests/test_lua_api/openmw.cfg @@ -1,4 +1,4 @@ -content=test_lua_api.omwscripts +content=test.omwscripts # Needed to test `core.getGMST` fallback=Water_RippleFrameCount,4 diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 0b481fba2b..18229a3cd8 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -6,7 +6,6 @@ local input = require('openmw.input') local types = require('openmw.types') local nearby = require('openmw.nearby') local camera = require('openmw.camera') -local matchers = require('matchers') types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Controls, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) @@ -41,7 +40,7 @@ local function rotateByPitch(object, target) rotate(object, target, nil) end -testing.registerLocalTest('player yaw rotation', +testing.registerLocalTest('playerYawRotation', function() local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ() local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX() @@ -61,7 +60,7 @@ testing.registerLocalTest('player yaw rotation', testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change') end) -testing.registerLocalTest('player pitch rotation', +testing.registerLocalTest('playerPitchRotation', function() local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ() local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX() @@ -81,7 +80,7 @@ testing.registerLocalTest('player pitch rotation', testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention') end) -testing.registerLocalTest('player pitch and yaw rotation', +testing.registerLocalTest('playerPitchAndYawRotation', function() local targetPitch = math.rad(-30) local targetYaw = math.rad(-60) @@ -100,7 +99,7 @@ testing.registerLocalTest('player pitch and yaw rotation', testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention') end) -testing.registerLocalTest('player rotation', +testing.registerLocalTest('playerRotation', function() local rotation = math.sqrt(2) local endTime = core.getSimulationTime() + 3 @@ -114,17 +113,17 @@ testing.registerLocalTest('player rotation', coroutine.yield() local alpha1, gamma1 = self.rotation:getAnglesXZ() - testing.expectThat(alpha1, matchers.isNotNan(), 'Alpha rotation in XZ convention is nan') - testing.expectThat(gamma1, matchers.isNotNan(), 'Gamma rotation in XZ convention is nan') + testing.expectThat(alpha1, testing.isNotNan(), 'Alpha rotation in XZ convention is nan') + testing.expectThat(gamma1, testing.isNotNan(), 'Gamma rotation in XZ convention is nan') local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX() - testing.expectThat(alpha2, matchers.isNotNan(), 'Alpha rotation in ZYX convention is nan') - testing.expectThat(beta2, matchers.isNotNan(), 'Beta rotation in ZYX convention is nan') - testing.expectThat(gamma2, matchers.isNotNan(), 'Gamma rotation in ZYX convention is nan') + testing.expectThat(alpha2, testing.isNotNan(), 'Alpha rotation in ZYX convention is nan') + testing.expectThat(beta2, testing.isNotNan(), 'Beta rotation in ZYX convention is nan') + testing.expectThat(gamma2, testing.isNotNan(), 'Gamma rotation in ZYX convention is nan') end end) -testing.registerLocalTest('player forward running', +testing.registerLocalTest('playerForwardRunning', function() local startPos = self.position local endTime = core.getSimulationTime() + 1 @@ -142,7 +141,7 @@ testing.registerLocalTest('player forward running', testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord') end) -testing.registerLocalTest('player diagonal walking', +testing.registerLocalTest('playerDiagonalWalking', function() local startPos = self.position local endTime = core.getSimulationTime() + 1 @@ -221,7 +220,7 @@ testing.registerLocalTest('findNearestNavMeshPosition', 'Navigation mesh position ' .. testing.formatActualExpected(result, expected)) end) -testing.registerLocalTest('player memory limit', +testing.registerLocalTest('playerMemoryLimit', function() local ok, err = pcall(function() local str = 'a' @@ -233,7 +232,7 @@ testing.registerLocalTest('player memory limit', testing.expectEqual(err, 'not enough memory') end) -testing.registerLocalTest('player weapon attack', +testing.registerLocalTest('playerWeaponAttack', function() camera.setMode(camera.MODE.ThirdPerson) @@ -316,8 +315,7 @@ testing.registerLocalTest('player weapon attack', self.controls.run = true self.controls.movement = 1 else - local halfExtents = types.Actor.getPathfindingAgentBounds(targetActor).halfExtents - destination = targetActor.position - util.vector3(0, 0, halfExtents.z) + destination = targetActor.position if nextTime < time then if use == 0 then @@ -347,5 +345,5 @@ return { engineHandlers = { onFrame = testing.updateLocal, }, - eventHandlers = testing.localEventHandlers, + eventHandlers = testing.eventHandlers } diff --git a/scripts/data/integration_tests/test_lua_api/global.lua b/scripts/data/integration_tests/test_lua_api/test.lua similarity index 79% rename from scripts/data/integration_tests/test_lua_api/global.lua rename to scripts/data/integration_tests/test_lua_api/test.lua index 225660b858..ff2cd9bb33 100644 --- a/scripts/data/integration_tests/test_lua_api/global.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -7,7 +7,7 @@ local vfs = require('openmw.vfs') local world = require('openmw.world') local I = require('openmw.interfaces') -testing.registerGlobalTest('timers', function() +local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result') @@ -39,10 +39,9 @@ testing.registerGlobalTest('timers', function() testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed') testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed') testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed') -end) +end -testing.registerGlobalTest('teleport', function() - local player = world.players[1] +local function testTeleport() player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90))) coroutine.yield() testing.expect(player.cell.isExterior, 'teleport to exterior failed') @@ -72,16 +71,16 @@ testing.registerGlobalTest('teleport', function() 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:getYaw(), math.rad(-90), 0.05, 'teleporting changes rotation') -end) +end -testing.registerGlobalTest('getGMST', function() +local function testGetGMST() testing.expectEqual(core.getGMST('non-existed gmst'), nil) testing.expectEqual(core.getGMST('Water_RippleFrameCount'), 4) testing.expectEqual(core.getGMST('Inventory_DirectionalDiffuseR'), 0.5) testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') -end) +end -testing.registerGlobalTest('MWScript', function() +local function testMWScript() local variableStoreCount = 18 local variableStore = world.mwscript.getGlobalVariables(player) testing.expectEqual(variableStoreCount, #variableStore) @@ -101,7 +100,7 @@ testing.registerGlobalTest('MWScript', function() indexCheck = indexCheck + 1 end testing.expectEqual(variableStoreCount, indexCheck) -end) +end local function testRecordStore(store, storeName, skipPairs) testing.expect(store.records) @@ -122,7 +121,7 @@ local function testRecordStore(store, storeName, skipPairs) testing.expectEqual(status, true, storeName) end -testing.registerGlobalTest('record stores', function() +local function testRecordStores() for key, type in pairs(types) do if type.records then testRecordStore(type, key) @@ -141,9 +140,9 @@ testing.registerGlobalTest('record stores', function() testRecordStore(types.NPC.classes, "classes") testRecordStore(types.NPC.races, "races") testRecordStore(types.Player.birthSigns, "birthSigns") -end) +end -testing.registerGlobalTest('record creation', function() +local function testRecordCreation() local newLight = { isCarriable = true, isDynamic = true, @@ -166,9 +165,9 @@ testing.registerGlobalTest('record creation', function() for key, value in pairs(newLight) do testing.expectEqual(record[key], value) end -end) +end -testing.registerGlobalTest('UTF-8 characters', function() +local function testUTF8Chars() testing.expectEqual(utf8.codepoint("😀"), 0x1F600) local chars = {} @@ -193,9 +192,9 @@ testing.registerGlobalTest('UTF-8 characters', function() testing.expectEqual(utf8.codepoint(char), codepoint) testing.expectEqual(utf8.len(char), 1) end -end) +end -testing.registerGlobalTest('UTF-8 strings', function() +local function testUTF8Strings() local utf8str = "Hello, 你好, 🌎!" local str = "" @@ -206,9 +205,9 @@ testing.registerGlobalTest('UTF-8 strings', function() testing.expectEqual(utf8.len(utf8str), 13) testing.expectEqual(utf8.offset(utf8str, 9), 11) -end) +end -testing.registerGlobalTest('memory limit', function() +local function testMemoryLimit() local ok, err = pcall(function() local t = {} local n = 1 @@ -219,16 +218,14 @@ testing.registerGlobalTest('memory limit', function() end) testing.expectEqual(ok, false, 'Script reaching memory limit should fail') testing.expectEqual(err, 'not enough memory') -end) - -local function initPlayer() - local player = world.players[1] - player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity) - coroutine.yield() - return player end -testing.registerGlobalTest('vfs', function() +local function initPlayer() + player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity) + coroutine.yield() +end + +local function testVFS() local file = 'test_vfs_dir/lines.txt' local nosuchfile = 'test_vfs_dir/nosuchfile' testing.expectEqual(vfs.fileExists(file), true, 'lines.txt should exist') @@ -272,11 +269,12 @@ testing.registerGlobalTest('vfs', function() for _,v in pairs(expectedLines) do testing.expectEqual(getLine(), v) end -end) +end -testing.registerGlobalTest('commit crime', function() - local player = initPlayer() - testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `commit crime`') +local function testCommitCrime() + initPlayer() + local player = world.players[1] + testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `testCommitCrime`') testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts') -- Reset crime level to have a clean slate @@ -294,59 +292,82 @@ testing.registerGlobalTest('commit crime', function() types.Player.setCrimeLevel(player, 0) testing.expectEqual(I.Crimes.commitCrime(player, { victim = victim, type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, true, "Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in") testing.expectEqual(types.Player.getCrimeLevel(player), 0, "Crime level should not change if the victim's alarm value is low and there's no other witnesses") -end) - -testing.registerGlobalTest('record model property', function() - local player = world.players[1] - testing.expectEqual(types.NPC.record(player).model, 'meshes/basicplayer.dae') -end) - -local function registerPlayerTest(name) - testing.registerGlobalTest(name, function() - local player = initPlayer() - testing.runLocalTest(player, name) - end) end -registerPlayerTest('player yaw rotation') -registerPlayerTest('player pitch rotation') -registerPlayerTest('player pitch and yaw rotation') -registerPlayerTest('player rotation') -registerPlayerTest('player forward running') -registerPlayerTest('player diagonal walking') -registerPlayerTest('findPath') -registerPlayerTest('findRandomPointAroundCircle') -registerPlayerTest('castNavigationRay') -registerPlayerTest('findNearestNavMeshPosition') -registerPlayerTest('player memory limit') - -testing.registerGlobalTest('player weapon attack', function() - local player = initPlayer() - world.createObject('basic_dagger1h', 1):moveInto(player) - testing.runLocalTest(player, 'player weapon attack') -end) - -testing.registerGlobalTest('load while teleporting - init player', function() +local function testRecordModelProperty() + initPlayer() local player = world.players[1] - player:teleport('Museum of Wonders', util.vector3(0, -1500, 111), util.transform.rotateZ(math.rad(180))) -end) + testing.expectEqual(types.NPC.record(player).model, 'meshes/basicplayer.dae') +end -testing.registerGlobalTest('load while teleporting - teleport', function() - local player = world.players[1] - local landracer = world.createObject('landracer') - landracer:teleport(player.cell, player.position + util.vector3(0, 500, 0)) - coroutine.yield() - - local door = world.getObjectByFormId(core.getFormId('the_hub.omwaddon', 26)) - door:activateBy(player) - coroutine.yield() - - landracer:teleport(player.cell, player.position) -end) +tests = { + {'timers', testTimers}, + {'rotating player with controls.yawChange should change rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerYawRotation') + end}, + {'rotating player with controls.pitchChange should change rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerPitchRotation') + end}, + {'rotating player with controls.pitchChange and controls.yawChange should change rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerPitchAndYawRotation') + end}, + {'rotating player should not lead to nan rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerRotation') + end}, + {'playerForwardRunning', function() + initPlayer() + testing.runLocalTest(player, 'playerForwardRunning') + end}, + {'playerDiagonalWalking', function() + initPlayer() + testing.runLocalTest(player, 'playerDiagonalWalking') + end}, + {'findPath', function() + initPlayer() + testing.runLocalTest(player, 'findPath') + end}, + {'findRandomPointAroundCircle', function() + initPlayer() + testing.runLocalTest(player, 'findRandomPointAroundCircle') + end}, + {'castNavigationRay', function() + initPlayer() + testing.runLocalTest(player, 'castNavigationRay') + end}, + {'findNearestNavMeshPosition', function() + initPlayer() + testing.runLocalTest(player, 'findNearestNavMeshPosition') + end}, + {'teleport', testTeleport}, + {'getGMST', testGetGMST}, + {'recordStores', testRecordStores}, + {'recordCreation', testRecordCreation}, + {'utf8Chars', testUTF8Chars}, + {'utf8Strings', testUTF8Strings}, + {'mwscript', testMWScript}, + {'testMemoryLimit', testMemoryLimit}, + {'playerMemoryLimit', function() + initPlayer() + testing.runLocalTest(player, 'playerMemoryLimit') + end}, + {'player with equipped weapon on attack should damage health of other actors', function() + initPlayer() + world.createObject('basic_dagger1h', 1):moveInto(player) + testing.runLocalTest(player, 'playerWeaponAttack') + end}, + {'vfs', testVFS}, + {'testCommitCrime', testCommitCrime}, + {'recordModelProperty', testRecordModelProperty}, +} return { engineHandlers = { - onUpdate = testing.updateGlobal, + onUpdate = testing.testRunner(tests), + onPlayerAdded = function(p) player = p end, }, - eventHandlers = testing.globalEventHandlers, + 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..80507392f7 --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/test.omwscripts @@ -0,0 +1,2 @@ +GLOBAL: test.lua +PLAYER: player.lua diff --git a/scripts/data/integration_tests/test_lua_api/test_lua_api.omwscripts b/scripts/data/integration_tests/test_lua_api/test_lua_api.omwscripts deleted file mode 100644 index 4ce925e61d..0000000000 --- a/scripts/data/integration_tests/test_lua_api/test_lua_api.omwscripts +++ /dev/null @@ -1,3 +0,0 @@ -MENU: menu.lua -GLOBAL: global.lua -PLAYER: player.lua diff --git a/scripts/data/integration_tests/testing_util/matchers.lua b/scripts/data/integration_tests/testing_util/matchers.lua deleted file mode 100644 index c7643af206..0000000000 --- a/scripts/data/integration_tests/testing_util/matchers.lua +++ /dev/null @@ -1,218 +0,0 @@ -local module = {} - ---- --- Matcher verifying that distance between given value and expected is not greater than maxDistance. --- @function elementsAreArray --- @param expected#vector. --- @usage --- expectThat(util.vector2(0, 0), closeToVector(util.vector2(0, 1), 1)) -function module.closeToVector(expected, maxDistance) - return function(actual) - local distance = (expected - actual):length() - if distance <= maxDistance then - return '' - end - return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance) - end -end - ---- --- Matcher verifying that given value is an array each element of which matches elements of expected. --- @function elementsAreArray --- @param expected#array of values or matcher functions. --- @usage --- local t = {42, 13} --- local matcher = function(actual) --- if actual ~= 42 then --- return string.format('%s is not 42', actual) --- end --- return '' --- end --- expectThat({42, 13}, elementsAreArray({matcher, 13})) -function module.elementsAreArray(expected) - local expected_matchers = {} - for i, v in ipairs(expected) do - if type(v) == 'function' then - expected_matchers[i] = v - else - expected_matchers[i] = function (other) - if expected[i].__eq(expected[i], other) then - return '' - end - return string.format('%s element %s does no match expected: %s', i, other, expected[i]) - end - end - end - return function(actual) - if #actual < #expected_matchers then - return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers) - end - local message = '' - for i, v in ipairs(actual) do - if i > #expected_matchers then - message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers) - break - end - local match_message = expected_matchers[i](v) - if match_message ~= '' then - message = string.format('%s\n%s', message, match_message) - end - end - return message - end -end - ---- --- Matcher verifying that given number is not a nan. --- @function isNotNan --- @usage --- expectThat(value, isNotNan()) -function module.isNotNan() - return function(actual) - if actual ~= actual then - return 'actual value is nan, expected to be not nan' - end - return '' - end -end - ---- --- Matcher accepting any value. --- @function isAny --- @usage --- expectThat(value, isAny()) -function module.isAny() - return function(actual) - return '' - end -end - -local function serializeArray(a) - local result = nil - for _, v in ipairs(a) do - if result == nil then - result = string.format('{%s', serialize(v)) - else - result = string.format('%s, %s', result, serialize(v)) - end - end - if result == nil then - return '{}' - end - return string.format('%s}', result) -end - -local function serializeTable(t) - local result = nil - for k, v in pairs(t) do - if result == nil then - result = string.format('{%q = %s', k, serialize(v)) - else - result = string.format('%s, %q = %s', result, k, serialize(v)) - end - end - if result == nil then - return '{}' - end - return string.format('%s}', result) -end - -local function isArray(t) - local i = 1 - for _ in pairs(t) do - if t[i] == nil then - return false - end - i = i + 1 - end - return true -end - -function serialize(v) - local t = type(v) - if t == 'string' then - return string.format('%q', v) - elseif t == 'table' then - if isArray(v) then - return serializeArray(v) - end - return serializeTable(v) - end - return string.format('%s', v) -end - -local function compareScalars(v1, v2) - if v1 == v2 then - return '' - end - if type(v1) == 'string' then - return string.format('%q ~= %q', v1, v2) - end - return string.format('%s ~= %s', v1, v2) -end - -local function collectKeys(t) - local result = {} - for key in pairs(t) do - table.insert(result, key) - end - table.sort(result) - return result -end - -local function compareTables(t1, t2) - local keys1 = collectKeys(t1) - local keys2 = collectKeys(t2) - if #keys1 ~= #keys2 then - return string.format('table size mismatch: %d ~= %d', #keys1, #keys2) - end - for i = 1, #keys1 do - local key1 = keys1[i] - local key2 = keys2[i] - if key1 ~= key2 then - return string.format('table keys mismatch: %q ~= %q', key1, key2) - end - local d = compare(t1[key1], t2[key2]) - if d ~= '' then - return string.format('table values mismatch at key %s: %s', serialize(key1), d) - end - end - return '' -end - -function compare(v1, v2) - local type1 = type(v1) - local type2 = type(v2) - if type2 == 'function' then - return v2(v1) - end - if type1 ~= type2 then - return string.format('types mismatch: %s ~= %s', type1, type2) - end - if type1 == 'nil' then - return '' - elseif type1 == 'table' then - return compareTables(v1, v2) - elseif type1 == 'nil' or type1 == 'boolean' or type1 == 'number' or type1 == 'string' then - return compareScalars(v1, v2) - end - error('unsupported type: %s', type1) -end - ---- --- Matcher verifying that given value is equal to expected. Accepts nil, boolean, number, string and table or matcher --- function. --- @function equalTo --- @usage --- expectThat({a = {42, 'foo', {b = true}}}, equalTo({a = {42, 'foo', {b = true}}})) -function module.equalTo(expected) - return function(actual) - local diff = compare(actual, expected) - if diff == '' then - return '' - end - return string.format('%s; actual: %s; expected: %s', diff, serialize(actual, ''), serialize(expected, '')) - end -end - -return module diff --git a/scripts/data/integration_tests/testing_util/testing_util.lua b/scripts/data/integration_tests/testing_util/testing_util.lua index c894fa96f4..7b886636ed 100644 --- a/scripts/data/integration_tests/testing_util/testing_util.lua +++ b/scripts/data/integration_tests/testing_util/testing_util.lua @@ -2,22 +2,23 @@ local core = require('openmw.core') local util = require('openmw.util') local M = {} - -local menuTestsOrder = {} -local menuTests = {} - -local globalTestsOrder = {} -local globalTests = {} -local globalTestRunner = nil -local currentGlobalTest = nil -local currentGlobalTestError = nil - -local localTests = {} -local localTestRunner = nil local currentLocalTest = nil local currentLocalTestError = nil -local function makeTestCoroutine(fn) +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 @@ -26,64 +27,6 @@ local function makeTestCoroutine(fn) end end -local function runTests(tests) - 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 - -function M.makeUpdateMenu() - return makeTestCoroutine(function() - print('Running menu tests...') - runTests(menuTestsOrder) - end) -end - -function M.makeUpdateGlobal() - return makeTestCoroutine(function() - print('Running global tests...') - runTests(globalTestsOrder) - end) -end - -function M.registerMenuTest(name, fn) - menuTests[name] = fn - table.insert(menuTestsOrder, {name, fn}) -end - -function M.runGlobalTest(name) - currentGlobalTest = name - currentGlobalTestError = nil - core.sendGlobalEvent('runGlobalTest', name) - while currentGlobalTest do - coroutine.yield() - end - if currentGlobalTestError then - error(currentGlobalTestError, 2) - end -end - -function M.registerGlobalTest(name, fn) - globalTests[name] = fn - table.insert(globalTestsOrder, {name, fn}) -end - -function M.updateGlobal() - if globalTestRunner and coroutine.status(globalTestRunner) ~= 'dead' then - coroutine.resume(globalTestRunner) - else - globalTestRunner = nil - end -end - function M.runLocalTest(obj, name) currentLocalTest = name currentLocalTestError = nil @@ -96,21 +39,7 @@ function M.runLocalTest(obj, name) end end -function M.registerLocalTest(name, fn) - localTests[name] = fn -end - -function M.updateLocal() - if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then - if not core.isWorldPaused() then - coroutine.resume(localTestRunner) - end - else - localTestRunner = nil - end -end - -function M.expect(cond, msg) +function M.expect(cond, delta, msg) if not cond then error(msg or '"true" expected', 2) end @@ -158,6 +87,76 @@ function M.expectNotEqual(v1, v2, msg) end end +function M.closeToVector(expected, maxDistance) + return function(actual) + local distance = (expected - actual):length() + if distance <= maxDistance then + return '' + end + return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance) + end +end + +--- +-- Matcher verifying that given value is an array each element of which matches elements of expected. +-- @function elementsAreArray +-- @param expected#array of values or matcher functions. +-- @usage +-- local t = {42, 13} +-- local matcher = function(actual) +-- if actual ~= 42 then +-- return string.format('%s is not 42', actual) +-- end +-- return '' +-- end +-- expectThat({42, 13}, elementsAreArray({matcher, 13})) +function M.elementsAreArray(expected) + local expected_matchers = {} + for i, v in ipairs(expected) do + if type(v) == 'function' then + expected_matchers[i] = v + else + expected_matchers[i] = function (other) + if expected[i].__eq(expected[i], other) then + return '' + end + return string.format('%s element %s does no match expected: %s', i, other, expected[i]) + end + end + end + return function(actual) + if #actual < #expected_matchers then + return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers) + end + local message = '' + for i, v in ipairs(actual) do + if i > #expected_matchers then + message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers) + break + end + local match_message = expected_matchers[i](v) + if match_message ~= '' then + message = string.format('%s\n%s', message, match_message) + end + end + return message + end +end + +--- +-- Matcher verifying that given number is not a nan. +-- @function isNotNan +-- @usage +-- expectThat(value, isNotNan()) +function M.isNotNan(expected) + return function(actual) + if actual ~= actual then + return 'actual value is nan, expected to be not nan' + end + return '' + end +end + --- -- Verifies that given value matches provided matcher. -- @function expectThat @@ -183,50 +182,28 @@ function M.formatActualExpected(actual, expected) return string.format('actual: %s, expected: %s', actual, expected) end --- used only in menu scripts -M.menuEventHandlers = { - globalTestFinished = function(data) - if data.name ~= currentGlobalTest then - error(string.format('globalTestFinished with incorrect name %s, expected %s', data.name, currentGlobalTest), 2) - end - currentGlobalTest = nil - currentGlobalTestError = data.errMsg - end, -} +local localTests = {} +local localTestRunner = nil --- used only in global scripts -M.globalEventHandlers = { - runGlobalTest = function(name) - fn = globalTests[name] - local types = require('openmw.types') - local world = require('openmw.world') - if not fn then - types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg='Global test is not found'}) - return - end - globalTestRunner = coroutine.create(function() - local status, err = pcall(fn) - if status then - err = nil - end - types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg=err}) - end) - end, - localTestFinished = function(data) - if data.name ~= currentLocalTest then - error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest), 2) - end - currentLocalTest = nil - currentLocalTestError = data.errMsg - end, -} +function M.registerLocalTest(name, fn) + localTests[name] = fn +end --- used only in local scripts -M.localEventHandlers = { - runLocalTest = function(name) +function M.updateLocal() + if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then + if not core.isWorldPaused() then + coroutine.resume(localTestRunner) + end + 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='Local test is not found'}) + core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Test not found'}) return end localTestRunner = coroutine.create(function() @@ -237,6 +214,13 @@ M.localEventHandlers = { 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/data/morrowind_tests/global.lua b/scripts/data/morrowind_tests/global.lua index f17e72cda9..fb7113d1b1 100644 --- a/scripts/data/morrowind_tests/global.lua +++ b/scripts/data/morrowind_tests/global.lua @@ -8,13 +8,28 @@ if not core.contentFiles.has('Morrowind.esm') then error('This test requires Morrowind.esm') end -require('global_issues') -require('global_dialogues') -require('global_mwscript') +function makeTests(modules) + local tests = {} + + for _, moduleName in ipairs(modules) do + local module = require(moduleName) + for _, v in ipairs(module) do + table.insert(tests, {string.format('[%s] %s', moduleName, v[1]), v[2]}) + end + end + + return tests +end + +local testModules = { + 'global_issues', + 'global_dialogues', + 'global_mwscript', +} return { engineHandlers = { - onUpdate = testing.makeUpdateGlobal(), + onUpdate = testing.testRunner(makeTests(testModules)), }, - eventHandlers = testing.globalEventHandlers, + eventHandlers = testing.eventHandlers, } diff --git a/scripts/data/morrowind_tests/global_dialogues.lua b/scripts/data/morrowind_tests/global_dialogues.lua index 68f4f4a747..397eb8461c 100644 --- a/scripts/data/morrowind_tests/global_dialogues.lua +++ b/scripts/data/morrowind_tests/global_dialogues.lua @@ -13,37 +13,35 @@ function iterateOverRecords(records) return firstRecordId, lastRecordId, count end -testing.registerGlobalTest('[dialogues] Should support iteration over journal dialogues', function() - local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.journal.records) - testing.expectEqual(firstRecordId, '11111 test journal') - testing.expectEqual(lastRecordId, 'va_vamprich') - testing.expectEqual(count, 632) -end) - -testing.registerGlobalTest('[dialogues] Should support iteration over topic dialogues', function() - local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.topic.records) - testing.expectEqual(firstRecordId, '1000-drake pledge') - testing.expectEqual(lastRecordId, 'zenithar') - testing.expectEqual(count, 1698) -end) - -testing.registerGlobalTest('[dialogues] Should support iteration over greeting dialogues', function() - local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.greeting.records) - testing.expectEqual(firstRecordId, 'greeting 0') - testing.expectEqual(lastRecordId, 'greeting 9') - testing.expectEqual(count, 10) -end) - -testing.registerGlobalTest('[dialogues] Should support iteration over persuasion dialogues', function() - local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.persuasion.records) - testing.expectEqual(firstRecordId, 'admire fail') - testing.expectEqual(lastRecordId, 'taunt success') - testing.expectEqual(count, 10) -end) - -testing.registerGlobalTest('[dialogues] Should support iteration over voice dialogues', function() - local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.voice.records) - testing.expectEqual(firstRecordId, 'alarm') - testing.expectEqual(lastRecordId, 'thief') - testing.expectEqual(count, 8) -end) +return { + {'Should support iteration over journal dialogues', function() + local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.journal.records) + testing.expectEqual(firstRecordId, '11111 test journal') + testing.expectEqual(lastRecordId, 'va_vamprich') + testing.expectEqual(count, 632) + end}, + {'Should support iteration over topic dialogues', function() + local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.topic.records) + testing.expectEqual(firstRecordId, '1000-drake pledge') + testing.expectEqual(lastRecordId, 'zenithar') + testing.expectEqual(count, 1698) + end}, + {'Should support iteration over greeting dialogues', function() + local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.greeting.records) + testing.expectEqual(firstRecordId, 'greeting 0') + testing.expectEqual(lastRecordId, 'greeting 9') + testing.expectEqual(count, 10) + end}, + {'Should support iteration over persuasion dialogues', function() + local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.persuasion.records) + testing.expectEqual(firstRecordId, 'admire fail') + testing.expectEqual(lastRecordId, 'taunt success') + testing.expectEqual(count, 10) + end}, + {'Should support iteration over voice dialogues', function() + local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.voice.records) + testing.expectEqual(firstRecordId, 'alarm') + testing.expectEqual(lastRecordId, 'thief') + testing.expectEqual(count, 8) + end}, +} diff --git a/scripts/data/morrowind_tests/global_issues.lua b/scripts/data/morrowind_tests/global_issues.lua index a3400da87c..2afad085b0 100644 --- a/scripts/data/morrowind_tests/global_issues.lua +++ b/scripts/data/morrowind_tests/global_issues.lua @@ -4,37 +4,37 @@ local world = require('openmw.world') local core = require('openmw.core') local types = require('openmw.types') -testing.registerGlobalTest('[issues] Player should be able to walk up stairs in Ebonheart docks (#4247)', function() - world.players[1]:teleport('', util.vector3(19867, -102180, -79), util.transform.rotateZ(math.rad(91))) - coroutine.yield() - testing.runLocalTest(world.players[1], 'Player should be able to walk up stairs in Ebonheart docks (#4247)') -end) - -testing.registerGlobalTest('[issues] Guard in Imperial Prison Ship should find path (#7241)', function() - world.players[1]:teleport('Imperial Prison Ship', util.vector3(61, -135, -105), util.transform.rotateZ(math.rad(-20))) - coroutine.yield() - testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)') -end) - -testing.registerGlobalTest('[issues] Should keep reference to an object moved into container (#7663)', function() - world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0))) - coroutine.yield() - local barrel = world.createObject('barrel_01', 1) - local fargothRing = world.createObject('ring_keley', 1) - coroutine.yield() - testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil) - fargothRing:moveInto(types.Container.inventory(barrel)) - coroutine.yield() - testing.expectEqual(fargothRing.recordId, 'ring_keley') - local isFargothRing = function(actual) - if actual == nil then - return 'ring_keley is not found' +return { + {'Player should be able to walk up stairs in Ebonheart docks (#4247)', function() + world.players[1]:teleport('', util.vector3(19867, -102180, -79), util.transform.rotateZ(math.rad(91))) + coroutine.yield() + testing.runLocalTest(world.players[1], 'Player should be able to walk up stairs in Ebonheart docks (#4247)') + end}, + {'Guard in Imperial Prison Ship should find path (#7241)', function() + world.players[1]:teleport('Imperial Prison Ship', util.vector3(61, -135, -105), util.transform.rotateZ(math.rad(-20))) + coroutine.yield() + testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)') + end}, + {'Should keep reference to an object moved into container (#7663)', function() + world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0))) + coroutine.yield() + local barrel = world.createObject('barrel_01', 1) + local fargothRing = world.createObject('ring_keley', 1) + coroutine.yield() + testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil) + fargothRing:moveInto(types.Container.inventory(barrel)) + coroutine.yield() + testing.expectEqual(fargothRing.recordId, 'ring_keley') + local isFargothRing = function(actual) + if actual == nil then + return 'ring_keley is not found' + end + if actual.id ~= fargothRing.id then + return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id) + .. ', expected=' .. tostring(fargothRing.id) + end + return '' end - if actual.id ~= fargothRing.id then - return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id) - .. ', expected=' .. tostring(fargothRing.id) - end - return '' - end - testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing) -end) + testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing) + end}, +} diff --git a/scripts/data/morrowind_tests/global_mwscript.lua b/scripts/data/morrowind_tests/global_mwscript.lua index fec9fcdba5..a4347ea66e 100644 --- a/scripts/data/morrowind_tests/global_mwscript.lua +++ b/scripts/data/morrowind_tests/global_mwscript.lua @@ -14,38 +14,38 @@ function iterateOverVariables(variables) return first, last, count end -testing.registerGlobalTest('[mwscript] Should support iteration over an empty set of script variables', function() - local mainVars = world.mwscript.getGlobalScript('main').variables - local first, last, count = iterateOverVariables(mainVars) - testing.expectEqual(first, nil) - testing.expectEqual(last, nil) - testing.expectEqual(count, 0) - testing.expectEqual(count, #mainVars) -end) +return { + {'Should support iteration over an empty set of script variables', function() + local mainVars = world.mwscript.getGlobalScript('main').variables + local first, last, count = iterateOverVariables(mainVars) + testing.expectEqual(first, nil) + testing.expectEqual(last, nil) + testing.expectEqual(count, 0) + testing.expectEqual(count, #mainVars) + end}, + {'Should support iteration of script variables', function() + local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) + local jiubVars = world.mwscript.getLocalScript(jiub).variables + local first, last, count = iterateOverVariables(jiubVars) -testing.registerGlobalTest('[mwscript] Should support iteration of script variables', function() - local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) - local jiubVars = world.mwscript.getLocalScript(jiub).variables - local first, last, count = iterateOverVariables(jiubVars) + testing.expectEqual(first, 'state') + testing.expectEqual(last, 'timer') + testing.expectEqual(count, 3) + testing.expectEqual(count, #jiubVars) + end}, + {'Should support numeric and string indices for getting and setting', function() + local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) + local jiubVars = world.mwscript.getLocalScript(jiub).variables - testing.expectEqual(first, 'state') - testing.expectEqual(last, 'timer') - testing.expectEqual(count, 3) - testing.expectEqual(count, #jiubVars) -end) + testing.expectEqual(jiubVars[1], jiubVars.state) + testing.expectEqual(jiubVars[2], jiubVars.wandering) + testing.expectEqual(jiubVars[3], jiubVars.timer) -testing.registerGlobalTest('[mwscript] Should support numeric and string indices for getting and setting', function() - local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) - local jiubVars = world.mwscript.getLocalScript(jiub).variables - - testing.expectEqual(jiubVars[1], jiubVars.state) - testing.expectEqual(jiubVars[2], jiubVars.wandering) - testing.expectEqual(jiubVars[3], jiubVars.timer) - - jiubVars[1] = 123; - testing.expectEqual(jiubVars.state, 123) - jiubVars.wandering = 42; - testing.expectEqual(jiubVars[2], 42) - jiubVars[3] = 1.25; - testing.expectEqual(jiubVars.timer, 1.25) -end) + jiubVars[1] = 123; + testing.expectEqual(jiubVars.state, 123) + jiubVars.wandering = 42; + testing.expectEqual(jiubVars[2], 42) + jiubVars[3] = 1.25; + testing.expectEqual(jiubVars.timer, 1.25) + end}, +} diff --git a/scripts/data/morrowind_tests/player.lua b/scripts/data/morrowind_tests/player.lua index fcb1126c1b..7435b49553 100644 --- a/scripts/data/morrowind_tests/player.lua +++ b/scripts/data/morrowind_tests/player.lua @@ -5,7 +5,6 @@ local testing = require('testing_util') local util = require('openmw.util') local types = require('openmw.types') local nearby = require('openmw.nearby') -local matchers = require('matchers') types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Jumping, false) @@ -46,33 +45,33 @@ testing.registerLocalTest('Guard in Imperial Prison Ship should find path (#7241 testing.expectLessOrEqual((util.vector2(path[#path].x, path[#path].y) - util.vector2(dst.x, dst.y)):length(), 1, 'Last path point x, y') testing.expectLessOrEqual(path[#path].z - dst.z, 20, 'Last path point z') if agentBounds.shapeType == nearby.COLLISION_SHAPE_TYPE.Aabb then - testing.expectThat(path, matchers.elementsAreArray({ - matchers.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), - matchers.closeToVector(util.vector3(15, 1102, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-112, 1110, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-67.99993896484375, 1421.2000732421875, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-33.999935150146484375, 1414.4000244140625, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-6.79993534088134765625, 1380.4000244140625, 85.094573974609375), 1e-1), - matchers.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), - matchers.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), - matchers.closeToVector(util.vector3(83.552001953125, 42.26399993896484375, -104.58989715576171875), 1e-1), - matchers.closeToVector(util.vector3(89, -105, -98.72841644287109375), 1e-1), - matchers.closeToVector(util.vector3(90, -90, -99.7056884765625), 1e-1), + testing.expectThat(path, testing.elementsAreArray({ + testing.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), + testing.closeToVector(util.vector3(15, 1102, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-112, 1110, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-67.99993896484375, 1421.2000732421875, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-33.999935150146484375, 1414.4000244140625, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-6.79993534088134765625, 1380.4000244140625, 85.094573974609375), 1e-1), + testing.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), + testing.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), + testing.closeToVector(util.vector3(83.552001953125, 42.26399993896484375, -104.58989715576171875), 1e-1), + testing.closeToVector(util.vector3(89, -105, -98.72841644287109375), 1e-1), + testing.closeToVector(util.vector3(90, -90, -99.7056884765625), 1e-1), })) elseif agentBounds.shapeType == nearby.COLLISION_SHAPE_TYPE.Cylinder then - testing.expectThat(path, matchers.elementsAreArray({ - matchers.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), - matchers.closeToVector(util.vector3(-13.5999355316162109375, 1060.800048828125, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-27.1999359130859375, 1081.2000732421875, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-81.59993743896484375, 1128.800048828125, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-101.99993896484375, 1156.0001220703125, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), - matchers.closeToVector(util.vector3(7, 1470, 114.73973846435546875), 1e-1), - matchers.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), - matchers.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), - matchers.closeToVector(util.vector3(95, 27, -104.83390045166015625), 1e-1), - matchers.closeToVector(util.vector3(90, -90, -104.83390045166015625), 1e-1), + testing.expectThat(path, testing.elementsAreArray({ + testing.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), + testing.closeToVector(util.vector3(-13.5999355316162109375, 1060.800048828125, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-27.1999359130859375, 1081.2000732421875, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-81.59993743896484375, 1128.800048828125, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-101.99993896484375, 1156.0001220703125, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), + testing.closeToVector(util.vector3(7, 1470, 114.73973846435546875), 1e-1), + testing.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), + testing.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), + testing.closeToVector(util.vector3(95, 27, -104.83390045166015625), 1e-1), + testing.closeToVector(util.vector3(90, -90, -104.83390045166015625), 1e-1), })) end end) @@ -81,5 +80,5 @@ return { engineHandlers = { onFrame = testing.updateLocal, }, - eventHandlers = testing.localEventHandlers, + eventHandlers = testing.eventHandlers } diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py index 80c97f8b73..17f89fbe2f 100755 --- a/scripts/integration_tests.py +++ b/scripts/integration_tests.py @@ -1,13 +1,6 @@ #!/usr/bin/env python3 -import argparse -import datetime -import os -import shutil -import subprocess -import sys -import time - +import argparse, datetime, os, subprocess, sys, shutil from pathlib import Path parser = argparse.ArgumentParser(description="OpenMW integration tests.") @@ -47,13 +40,12 @@ testing_util_dir = tests_dir / "testing_util" time_str = datetime.datetime.now().strftime("%Y-%m-%d-%H.%M.%S") -def run_test(test_name): - start = time.time() - print(f'[----------] Running tests from {test_name}') +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 / test_name + test_dir = tests_dir / name with open(config_dir / "openmw.cfg", "w", encoding="utf-8") as omw_cfg: for path in content_paths: omw_cfg.write(f'data="{path.parent}"\n') @@ -66,8 +58,10 @@ def run_test(test_name): ) for path in content_paths: omw_cfg.write(f'content={path.name}\n') - with open(test_dir / "openmw.cfg") as stream: - omw_cfg.write(stream.read()) + if (test_dir / "openmw.cfg").exists(): + omw_cfg.write(open(test_dir / "openmw.cfg").read()) + elif (test_dir / "test.omwscripts").exists(): + omw_cfg.write("content=test.omwscripts\n") with open(config_dir / "settings.cfg", "a", encoding="utf-8") as settings_cfg: settings_cfg.write( "[Video]\n" @@ -80,76 +74,61 @@ def run_test(test_name): f"memory limit = {1024 * 1024 * 256}\n" ) stdout_lines = list() + exit_ok = True test_success = True - fatal_errors = list() with subprocess.Popen( - [openmw_binary, "--replace=config", "--config", config_dir, "--no-grab"], + [openmw_binary, "--replace=config", "--config", config_dir, "--skip-menu", "--no-grab", "--no-sound"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8", env={ - "OPENMW_OSG_STATS_FILE": str(work_dir / f"{test_name}.{time_str}.osg_stats.log"), + "OPENMW_OSG_STATS_FILE": str(work_dir / f"{name}.{time_str}.osg_stats.log"), "OPENMW_OSG_STATS_LIST": "times", **os.environ, }, ) as process: quit_requested = False - running_test_number = None - running_test_name = None - count = 0 - failed_tests = list() - test_start = None for line in process.stdout: if args.verbose: sys.stdout.write(line) else: stdout_lines.append(line) - if "Quit requested by a Lua script" in line: + 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: - test_start = time.time() - number, name = line.split("TEST_START")[1].strip().split("\t", maxsplit=1) - running_test_number = int(number) - running_test_name = name - count += 1 - print(f"[ RUN ] {running_test_name}") + w = line.split("TEST_START")[1].split("\t") + print(f"TEST {w[2].strip()}\t\t", end="") elif "TEST_OK" in line: - duration = (time.time() - test_start) * 1000 - number, name = line.split("TEST_OK")[1].strip().split("\t", maxsplit=1) - assert running_test_number == int(number) - print(f"[ OK ] {running_test_name} ({duration:.3f} ms)") + print(f"OK") elif "TEST_FAILED" in line: - duration = (time.time() - test_start) * 1000 - number, name, error = line.split("TEST_FAILED")[1].strip().split("\t", maxsplit=2) - assert running_test_number == int(number) - print(error) - print(f"[ FAILED ] {running_test_name} ({duration:.3f} ms)") - failed_tests.append(running_test_name) + w = line.split("TEST_FAILED")[1].split("\t") + print(f"FAILED {w[3]}\t\t") + test_success = False process.wait(5) if not quit_requested: - fatal_errors.append("unexpected termination") + print("ERROR: Unexpected termination") + exit_ok = False if process.returncode != 0: - fatal_errors.append(f"openmw exited with code {process.returncode}") + print(f"ERROR: openmw exited with code {process.returncode}") + exit_ok = False if os.path.exists(config_dir / "openmw.log"): - shutil.copyfile(config_dir / "openmw.log", work_dir / f"{test_name}.{time_str}.log") - if fatal_errors and not args.verbose: + shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log") + if not exit_ok and not args.verbose: sys.stdout.writelines(stdout_lines) - total_duration = (time.time() - start) * 1000 - print(f'\n[----------] {count} tests from {test_name} ({total_duration:.3f} ms total)') - print(f"[ PASSED ] {count - len(failed_tests)} tests.") - if fatal_errors: - print(f"[ FAILED ] fatal error: {'; '.join(fatal_errors)}") - if failed_tests: - print(f"[ FAILED ] {len(failed_tests)} tests, listed below:") - for failed_test in failed_tests: - print(f"[ FAILED ] {failed_test}") - return len(failed_tests) == 0 and not fatal_errors + if test_success and exit_ok: + print(f"{name} succeeded") + else: + print(f"{name} failed") + return test_success and exit_ok status = 0 for entry in tests_dir.glob("test_*"): if entry.is_dir(): - if not run_test(entry.name): + if not runTest(entry.name): status = -1 if status == 0: shutil.rmtree(config_dir, ignore_errors=True)