Compare commits

..

No commits in common. "master" and "openmw-49-rc2" have entirely different histories.

191 changed files with 1363 additions and 2311 deletions

View file

@ -1,15 +1,18 @@
Checks: > Checks: >
-*, -*,
boost-*,
portability-*, portability-*,
clang-analyzer-*, clang-analyzer-*,
-clang-analyzer-optin.*, -clang-analyzer-optin*,
-clang-analyzer-cplusplus.NewDeleteLeaks, -clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-cplusplus.NewDelete,
-clang-analyzer-core.CallAndMessage, -clang-analyzer-core.CallAndMessage,
modernize-avoid-bind, -modernize-avoid-bind
readability-identifier-naming WarningsAsErrors: >
WarningsAsErrors: '*' -*,
HeaderFilterRegex: '(apps|components)/' boost-*,
CheckOptions: portability-*,
- key: readability-identifier-naming.ConceptCase clang-analyzer-*,
value: CamelCase -clang-analyzer-optin*,
-clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-core.CallAndMessage
HeaderFilterRegex: '^(apps|components)'

View file

@ -202,11 +202,7 @@ jobs:
AWS_DEFAULT_REGION: eu-west-3 AWS_DEFAULT_REGION: eu-west-3
if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }} if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }}
working-directory: ${{ github.workspace }}/SymStore working-directory: ${{ github.workspace }}/SymStore
run: | run: aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym
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
- name: Add install directory to PATH - name: Add install directory to PATH
shell: bash shell: bash

View file

@ -512,16 +512,9 @@ Ubuntu_GCC_integration_tests_asan:
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done
- | - |
if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then 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'\/\\?*]/_}/" artifactDirectory="${CI_PROJECT_NAMESPACE//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_REF_NAME//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_SHORT_SHA//[\"<>|$'\t'\/\\?*]/_}-${CI_JOB_ID//[\"<>|$'\t'\/\\?*]/_}/"
for dmg in *.dmg; do 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 done
fi fi
- ccache -s - ccache -s
@ -547,7 +540,7 @@ macOS14_Xcode15_arm64:
script: script:
- apt-get update - apt-get update
- apt-get install -y curl gcab unzip - 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 - unzip -d awscli-exe-linux-x86_64 awscli-exe-linux-x86_64.zip
- pushd awscli-exe-linux-x86_64 - pushd awscli-exe-linux-x86_64
- ./aws/install - ./aws/install
@ -594,7 +587,7 @@ macOS14_Xcode15_arm64:
- choco install vswhere -y - choco install vswhere -y
- choco install ninja -y - choco install ninja -y
- choco install python -y - choco install python -y
- choco install awscli -y --version=2.22.35 - choco install awscli -y
- refreshenv - refreshenv
- | - |
function Make-SafeFileName { 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 - 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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- aws --version
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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 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) { 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} 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 .. Push-Location ..
..\CI\Store-Symbols.ps1 -SkipCompress ..\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 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 Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
@ -649,20 +637,13 @@ macOS14_Xcode15_arm64:
- | - |
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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} 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: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: ninja-2022-v12 key: ninja-2022-v11
paths: paths:
- ccache - ccache
- deps - deps
@ -757,7 +738,7 @@ macOS14_Xcode15_arm64:
- choco install 7zip -y - choco install 7zip -y
- choco install vswhere -y - choco install vswhere -y
- choco install python -y - choco install python -y
- choco install awscli -y --version=2.22.35 - choco install awscli -y
- refreshenv - refreshenv
- | - |
function Make-SafeFileName { 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 - 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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- aws --version
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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 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) { 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} 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 .. Push-Location ..
..\CI\Store-Symbols.ps1 -SkipCompress ..\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 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 Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
@ -807,20 +783,13 @@ macOS14_Xcode15_arm64:
- | - |
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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} 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: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: msbuild-2022-v12 key: msbuild-2022-v11
paths: paths:
- deps - deps
- MSVC2022_64/deps/Qt - MSVC2022_64/deps/Qt

View file

@ -35,6 +35,7 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex 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 #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 #6156: 1ft Charm or Sound magic effect vfx doesn't work properly
Bug #6190: Unintuitive sun specularity time of day dependence Bug #6190: Unintuitive sun specularity time of day dependence
@ -225,13 +226,6 @@
Bug #8231: AGOP doesn't like NiCollisionSwitch Bug #8231: AGOP doesn't like NiCollisionSwitch
Bug #8237: Non-bipedal creatures should *not* use spellcast equip/unequip animations Bug #8237: Non-bipedal creatures should *not* use spellcast equip/unequip animations
Bug #8252: Plugin dependencies are not required to be loaded 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 #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking
@ -316,8 +310,6 @@
Feature #8109: Expose commitCrime to Lua API Feature #8109: Expose commitCrime to Lua API
Feature #8130: Launcher: Add the ability to open a selected data directory in the file browser 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 #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 #5859: User openmw-cs.cfg has comment talking about settings.cfg
Task #5896: Do not use deprecated MyGUI properties Task #5896: Do not use deprecated MyGUI properties
Task #6085: Replace boost::filesystem with std::filesystem 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 #6066: Addtopic "return" does not work from within script. No errors thrown
Bug #6067: ESP loader fails for certain subrecord orders 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 #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 #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 #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
Bug #6109: Crash when playing a custom made menu_background file Bug #6109: Crash when playing a custom made menu_background file

View file

@ -7,7 +7,7 @@ export HOMEBREW_AUTOREMOVE=1
brew tap --repair brew tap --repair
brew update --quiet 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 ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake command -v cmake >/dev/null 2>&1 || brew install cmake

View file

@ -38,7 +38,7 @@ fi
if [[ $CI_CLANG_TIDY ]]; then if [[ $CI_CLANG_TIDY ]]; then
CMAKE_CONF_OPTS+=( CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_CLANG_TIDY=clang-tidy -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*"
-DBUILD_COMPONENTS_TESTS=ON -DBUILD_COMPONENTS_TESTS=ON
-DBUILD_OPENMW_TESTS=ON -DBUILD_OPENMW_TESTS=ON
-DBUILD_OPENCS_TESTS=ON -DBUILD_OPENCS_TESTS=ON

View file

@ -126,24 +126,10 @@ export APT_CACHE_DIR="${PWD}/apt-cache"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
set -x set -x
mkdir -pv "$APT_CACHE_DIR" mkdir -pv "$APT_CACHE_DIR"
apt-get update -yqq
while true; do
apt-get update -yqq && break
done
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null
add-apt-repository -y ppa:openmw/openmw
while true; do add-apt-repository -y ppa:openmw/openmw-daily
add-apt-repository -y ppa:openmw/openmw && break add-apt-repository -y ppa:openmw/staging
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
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null
apt list --installed apt list --installed

View file

@ -9,7 +9,7 @@ git checkout FETCH_HEAD
cd .. cd ..
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \ 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 ls integration_tests_output/*.osg_stats.log | while read v; do
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}" scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"

View file

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) 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_POSTPROCESSING_API_REVISION 2)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
@ -860,6 +860,7 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
set(BU_CHMOD_BUNDLE_ITEMS ON) set(BU_CHMOD_BUNDLE_ITEMS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
include(BundleUtilities) include(BundleUtilities)
cmake_minimum_required(VERSION 3.1)
" COMPONENT Runtime) " COMPONENT Runtime)
set(ABSOLUTE_PLUGINS "") set(ABSOLUTE_PLUGINS "")

View file

@ -179,7 +179,7 @@ namespace
generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
std::size_t n = 0; std::size_t n = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh);

View file

@ -104,7 +104,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random); std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serialize()); benchmark::DoNotOptimize(refIds[i].serialize());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -118,7 +118,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); }); = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); });
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -131,7 +131,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random); std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -145,7 +145,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -158,7 +158,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random); std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -172,7 +172,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -185,7 +185,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateIndexRefIds(random); std::vector<ESM::RefId> refIds = generateIndexRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -199,7 +199,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -212,7 +212,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random); std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -226,7 +226,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())

View file

@ -9,7 +9,7 @@ namespace
{ {
void settingsManager(benchmark::State& state) void settingsManager(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog")); benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog"));
} }
@ -17,7 +17,7 @@ namespace
void settingsManager2(benchmark::State& state) 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::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
@ -26,7 +26,7 @@ namespace
void settingsManager3(benchmark::State& state) 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::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
@ -36,7 +36,7 @@ namespace
void localStatic(benchmark::State& state) void localStatic(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
static float v = Settings::Manager::getFloat("sky blending start", "Fog"); static float v = Settings::Manager::getFloat("sky blending start", "Fog");
benchmark::DoNotOptimize(v); benchmark::DoNotOptimize(v);
@ -45,7 +45,7 @@ namespace
void localStatic2(benchmark::State& state) void localStatic2(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
@ -56,7 +56,7 @@ namespace
void localStatic3(benchmark::State& state) void localStatic3(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
@ -69,7 +69,7 @@ namespace
void settingsStorage(benchmark::State& state) void settingsStorage(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
float v = Settings::fog().mSkyBlendingStart.get(); float v = Settings::fog().mSkyBlendingStart.get();
benchmark::DoNotOptimize(v); benchmark::DoNotOptimize(v);
@ -78,7 +78,7 @@ namespace
void settingsStorage2(benchmark::State& state) void settingsStorage2(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
bool v1 = Settings::postProcessing().mTransparentPostpass.get(); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
float v2 = Settings::camera().mNearClip.get(); float v2 = Settings::camera().mNearClip.get();
@ -89,7 +89,7 @@ namespace
void settingsStorage3(benchmark::State& state) void settingsStorage3(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
bool v1 = Settings::postProcessing().mTransparentPostpass.get(); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
float v2 = Settings::camera().mNearClip.get(); float v2 = Settings::camera().mNearClip.get();
@ -102,7 +102,7 @@ namespace
void settingsStorageGet(benchmark::State& state) void settingsStorageGet(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start")); benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start"));
} }
@ -110,7 +110,7 @@ namespace
void settingsStorageGet2(benchmark::State& state) void settingsStorageGet2(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip")); benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));
@ -119,7 +119,7 @@ namespace
void settingsStorageGet3(benchmark::State& state) void settingsStorageGet3(benchmark::State& state)
{ {
for ([[maybe_unused]] auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip")); benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));

View file

@ -18,10 +18,6 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(bsatool gcov) target_link_libraries(bsatool gcov)
endif() endif()
if (WIN32)
install(TARGETS bsatool RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(bsatool PRIVATE target_precompile_headers(bsatool PRIVATE
<filesystem> <filesystem>

View file

@ -53,6 +53,8 @@ namespace
bpo::options_description makeOptionsDescription() bpo::options_description makeOptionsDescription()
{ {
using Fallback::FallbackMap;
bpo::options_description result; bpo::options_description result;
auto addOption = result.add_options(); auto addOption = result.add_options();
addOption("help", "print help message"); 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\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default"); "\n\twin1252 - Western European (Latin) alphabet, used by default");
addOption("fallback", addOption("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(),
bpo::value<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")->multitoken()->composing(),
"fallback values"); "fallback values");
Files::ConfigurationManager::addCommonOptions(result); Files::ConfigurationManager::addCommonOptions(result);

View file

@ -5,9 +5,7 @@
#include <components/detournavigator/makenavmesh.hpp> #include <components/detournavigator/makenavmesh.hpp>
#include <components/detournavigator/navmeshdbutils.hpp> #include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/serialization.hpp> #include <components/detournavigator/serialization.hpp>
#include <components/files/conversion.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/testing/util.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
@ -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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> 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 struct DetourNavigatorSpatialJobQueueTest : Test
{ {
const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) };

View file

@ -88,31 +88,4 @@ namespace
EXPECT_EQ(reader->getFileOffset(), sInitialOffset); EXPECT_EQ(reader->getFileOffset(), sInitialOffset);
} }
} }
TEST_F(ESM3ReadersCacheWithContentFile, CachedSizeAndName)
{
ESM::ReadersCache readers(2);
{
readers.get(0)->openRaw(std::make_unique<std::istringstream>("123"), "closed0.omwaddon");
readers.get(1)->openRaw(std::make_unique<std::istringstream>("12345"), "closed1.omwaddon");
readers.get(2)->openRaw(std::make_unique<std::istringstream>("1234567"), "free.omwaddon");
}
auto busy = readers.get(3);
busy->openRaw(std::make_unique<std::istringstream>("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);
}
} }

View file

@ -30,7 +30,7 @@ namespace
TEST(FilesGetHash, shouldClearErrors) TEST(FilesGetHash, shouldClearErrors)
{ {
const auto fileName = outputFilePath("fileName"); const auto fileName = temporaryFilePath("fileName");
std::string content; std::string content;
std::fill_n(std::back_inserter(content), 1, 'a'); std::fill_n(std::back_inserter(content), 1, 'a');
std::istringstream stream(content); std::istringstream stream(content);
@ -41,7 +41,7 @@ namespace
TEST_P(FilesGetHash, shouldReturnHashForStringStream) TEST_P(FilesGetHash, shouldReturnHashForStringStream)
{ {
const auto fileName = outputFilePath("fileName"); const auto fileName = temporaryFilePath("fileName");
std::string content; std::string content;
std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::fill_n(std::back_inserter(content), GetParam().mSize, 'a');
std::istringstream stream(content); std::istringstream stream(content);

View file

@ -38,10 +38,10 @@ namespace
sol::state lua; sol::state lua;
LuaUtil::InputAction::Registry registry; LuaUtil::InputAction::Registry registry;
LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description", 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); registry.insert(a);
LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description", 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); registry.insert(b);
LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) }); LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) });
LuaUtil::Callback bindBToA( LuaUtil::Callback bindBToA(

View file

@ -2,7 +2,6 @@
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/settings/parser.hpp> #include <components/settings/parser.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -25,9 +24,5 @@ int main(int argc, char** argv)
Settings::StaticValues::init(); Settings::StaticValues::init();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -51,7 +51,7 @@ namespace Misc
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesXZQuat[] = { const std::pair<osg::Quat, osg::Vec3f> eulerAnglesXZQuat[] = {
{ {
osg::Quat(1, 0, 0, 0), osg::Quat(1, 0, 0, 0),
osg::Vec3f(0, 0, osg::PIf), osg::Vec3f(0, 0, osg::PI),
}, },
{ {
osg::Quat(0, 1, 0, 0), osg::Quat(0, 1, 0, 0),
@ -59,7 +59,7 @@ namespace Misc
}, },
{ {
osg::Quat(0, 0, 1, 0), osg::Quat(0, 0, 1, 0),
osg::Vec3f(0, 0, osg::PIf), osg::Vec3f(0, 0, osg::PI),
}, },
{ {
osg::Quat(0, 0, 0, 1), osg::Quat(0, 0, 0, 1),
@ -128,15 +128,15 @@ namespace Misc
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesZYXQuat[] = { const std::pair<osg::Quat, osg::Vec3f> eulerAnglesZYXQuat[] = {
{ {
osg::Quat(1, 0, 0, 0), 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::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::Quat(0, 0, 1, 0),
osg::Vec3f(0, 0, osg::PIf), osg::Vec3f(0, 0, osg::PI),
}, },
{ {
osg::Quat(0, 0, 0, 1), osg::Quat(0, 0, 0, 1),

View file

@ -16,7 +16,7 @@ namespace
ShaderManager mManager; ShaderManager mManager;
ShaderManager::DefineMap mDefines; ShaderManager::DefineMap mDefines;
ShaderManagerTest() { mManager.setShaderPath(TestingOpenMW::outputDir()); } ShaderManagerTest() { mManager.setShaderPath("tests_output"); }
template <class F> template <class F>
void withShaderFile(const std::string& content, F&& f) void withShaderFile(const std::string& content, F&& f)

View file

@ -25,10 +25,6 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(esmtool gcov) target_link_libraries(esmtool gcov)
endif() endif()
if (WIN32)
install(TARGETS esmtool RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(esmtool PRIVATE target_precompile_headers(esmtool PRIVATE
<fstream> <fstream>

View file

@ -8,7 +8,6 @@
#include <QList> #include <QList>
#include <QMessageBox> #include <QMessageBox>
#include <QPair> #include <QPair>
#include <QProgressDialog>
#include <QPushButton> #include <QPushButton>
#include <algorithm> #include <algorithm>
@ -352,17 +351,9 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
if (!resourcesVfs.isEmpty()) if (!resourcesVfs.isEmpty())
directories.insert(0, { resourcesVfs }); 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<QString> visitedDirectories; std::unordered_set<QString> visitedDirectories;
for (const Config::SettingValue& currentDir : directories) for (const Config::SettingValue& currentDir : directories)
{ {
progressBar.setValue(progressBar.value() + 1);
if (!visitedDirectories.insert(currentDir.value).second) if (!visitedDirectories.insert(currentDir.value).second)
continue; continue;
@ -411,7 +402,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
// Add a "data file" icon if the directory contains a content file // Add a "data file" icon if the directory contains a content file
if (mSelector->containsDataFiles(currentDir.value)) if (mSelector->containsDataFiles(currentDir.value))
{ {
item->setIcon(containsDataIcon); item->setIcon(QIcon(":/images/openmw-plugin.png"));
tooltip << tr("Contains content file(s)"); tooltip << tr("Contains content file(s)");
} }
@ -774,7 +765,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
return; return;
QString rootPath = QFileDialog::getExistingDirectory( 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()) if (rootPath.isEmpty())
return; return;

View file

@ -1,16 +1,20 @@
set(NAVMESHTOOL_LIB set(NAVMESHTOOL
worldspacedata.cpp worldspacedata.cpp
navmesh.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) if (ANDROID)
add_library(openmw-navmeshtool SHARED main.cpp) add_library(openmw-navmeshtool SHARED
main.cpp
)
else() else()
openmw_add_executable(openmw-navmeshtool main.cpp) openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL})
endif() endif()
target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib) target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib)

View file

@ -62,6 +62,8 @@ namespace NavMeshTool
bpo::options_description makeOptionsDescription() bpo::options_description makeOptionsDescription()
{ {
using Fallback::FallbackMap;
bpo::options_description result; bpo::options_description result;
auto addOption = result.add_options(); auto addOption = result.add_options();
addOption("help", "print help message"); addOption("help", "print help message");
@ -223,8 +225,7 @@ namespace NavMeshTool
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
= DetourNavigator::makeSettingsFromSettingsManager(Debug::getRecastMaxLogLevel());
navigatorSettings.mRecast.mSwimHeightScale navigatorSettings.mRecast.mSwimHeightScale
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();

View file

@ -17,10 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(niftest gcov) target_link_libraries(niftest gcov)
endif() endif()
if (WIN32)
install(TARGETS niftest RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(niftest PRIVATE <filesystem>) target_precompile_headers(niftest PRIVATE <filesystem>)
endif() endif()

View file

@ -38,6 +38,8 @@
#include "view/doc/viewmanager.hpp" #include "view/doc/viewmanager.hpp"
using namespace Fallback;
CS::Editor::Editor(int argc, char** argv) CS::Editor::Editor(int argc, char** argv)
: mConfigVariables(readConfiguration()) : mConfigVariables(readConfiguration())
, mSettingsState(mCfgMgr) , mSettingsState(mCfgMgr)
@ -122,10 +124,7 @@ boost::program_options::variables_map CS::Editor::readConfiguration()
->default_value(std::vector<std::string>(), "fallback-archive") ->default_value(std::vector<std::string>(), "fallback-archive")
->multitoken()); ->multitoken());
addOption("fallback", addOption("fallback",
boost::program_options::value<Fallback::FallbackMap>() boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(),
->default_value(Fallback::FallbackMap(), "")
->multitoken()
->composing(),
"fallback values"); "fallback values");
Files::ConfigurationManager::addCommonOptions(desc); Files::ConfigurationManager::addCommonOptions(desc);
@ -142,7 +141,7 @@ std::pair<Files::PathContainer, std::vector<std::string>> CS::Editor::readConfig
{ {
boost::program_options::variables_map& variables = mConfigVariables; boost::program_options::variables_map& variables = mConfigVariables;
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
mEncodingName = variables["encoding"].as<std::string>(); mEncodingName = variables["encoding"].as<std::string>();
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));

View file

@ -1164,30 +1164,23 @@ void CSVDoc::View::onRequestFocus(const std::string& id)
QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position) QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position)
{ {
QScreen* screen = QApplication::screenAt(position); QScreen* screen = QApplication::screenAt(position);
if (screen) if (screen == nullptr)
return screen;
const QList<QScreen*> screens = QApplication::screens();
if (screens.isEmpty())
throw std::runtime_error("No screens available");
int closestDistance = std::numeric_limits<int>::max();
for (QScreen* candidate : screens)
{ {
const QRect geometry = candidate->geometry(); QPoint clampedPosition = position;
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;
if (distance < closestDistance) // If we failed to find the screen,
{ // clamp negative positions and try again
closestDistance = distance; if (clampedPosition.x() <= 0)
screen = candidate; clampedPosition.setX(0);
} if (clampedPosition.y() <= 0)
clampedPosition.setY(0);
screen = QApplication::screenAt(clampedPosition);
} }
if (screen == nullptr) 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; return screen;
} }

View file

@ -60,6 +60,24 @@
#include "pagedworldspacewidget.hpp" #include "pagedworldspacewidget.hpp"
#include "worldspacewidget.hpp" #include "worldspacewidget.hpp"
namespace
{
constexpr std::string_view sInstanceModeTooltip = R"(
Instance editing
<ul><li>Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances</li>
<li>Use {scene-edit-primary} to manipulate instances</li>
<li>Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap
selection relative to the reference object</li>
<li>Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, rotate, and
scale modes respectively</li>
<li>Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes
respectively</li>
<li>Use {scene-delete} to delete currently selected objects</li>
<li>Use {scene-duplicate} to duplicate instances</li>
<li>Use {scene-instance-drop} to drop instances</li></ul>
)";
}
int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const
{ {
return id == "move" ? 0 : (id == "rotate" ? 1 : 2); return id == "move" ? 0 : (id == "rotate" ? 1 : 2);
@ -294,28 +312,10 @@ void CSVRender::InstanceMode::setDragAxis(const char axis)
mDragAxis = newDragAxis; mDragAxis = newDragAxis;
} }
QString CSVRender::InstanceMode::getTooltip()
{
return QString(
"Instance editing"
"<ul><li>Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances</li>"
"<li>Use {scene-edit-primary} to manipulate instances</li>"
"<li>Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap "
"selection relative to the reference object</li>"
"<li>Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, "
"rotate, and "
"scale modes respectively</li>"
"<li>Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes "
"respectively</li>"
"<li>Use {scene-delete} to delete currently selected objects</li>"
"<li>Use {scene-duplicate} to duplicate instances</li>"
"<li>Use {scene-instance-drop} to drop instances</li></ul>");
}
CSVRender::InstanceMode::InstanceMode( CSVRender::InstanceMode::InstanceMode(
WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent) WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent)
: EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"), : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"),
Mask_Reference | Mask_Terrain, getTooltip(), parent) Mask_Reference | Mask_Terrain, sInstanceModeTooltip.data(), parent)
, mSubMode(nullptr) , mSubMode(nullptr)
, mSubModeId("move") , mSubModeId("move")
, mSelectionMode(nullptr) , mSelectionMode(nullptr)

View file

@ -53,7 +53,6 @@ namespace CSVRender
std::vector<osg::Vec3> mObjectsAtDragStart; std::vector<osg::Vec3> mObjectsAtDragStart;
CSMWorld::IdTable* mSelectionGroups; CSMWorld::IdTable* mSelectionGroups;
QString getTooltip();
int getSubModeFromId(const std::string& id) const; int getSubModeFromId(const std::string& id) const;
osg::Vec3 quatToEuler(const osg::Quat& quat) const; osg::Vec3 quatToEuler(const osg::Quat& quat) const;

View file

@ -761,17 +761,18 @@ void CSVRender::TerrainShapeMode::smoothHeight(
// this = this Cell // this = this Cell
// left = x - 1, up = y - 1, right = x + 1, down = y + 1 // left = x - 1, up = y - 1, right = x + 1, down = y + 1
// Altered = transient edit (in current edited) // 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 thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
float* thisAlteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); float leftHeight = 0.0f;
float thisAlteredHeight = thisAlteredHeightPtr != nullptr ? *thisAlteredHeightPtr : 0.f; float leftAlteredHeight = 0.0f;
float leftHeight = thisHeight; float upAlteredHeight = 0.0f;
float leftAlteredHeight = thisAlteredHeight; float rightHeight = 0.0f;
float rightHeight = thisHeight; float rightAlteredHeight = 0.0f;
float rightAlteredHeight = thisAlteredHeight; float downHeight = 0.0f;
float downHeight = thisHeight; float downAlteredHeight = 0.0f;
float downAlteredHeight = thisAlteredHeight; float upHeight = 0.0f;
float upHeight = thisHeight;
float upAlteredHeight = thisAlteredHeight;
if (allowLandShapeEditing(cellId)) if (allowLandShapeEditing(cellId))
{ {
@ -779,80 +780,70 @@ void CSVRender::TerrainShapeMode::smoothHeight(
if (inCellX == 0) if (inCellX == 0)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
if (isLandLoaded(cellId))
{
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>(); .value<CSMWorld::LandHeightsColumn::DataType>();
leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)];
float* alteredHeightPtr if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2))
= paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); leftAlteredHeight
leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY);
}
} }
if (inCellY == 0) if (inCellY == 0)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
if (isLandLoaded(cellId))
{
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer const CSMWorld::LandHeightsColumn::DataType landUpShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>(); .value<CSMWorld::LandHeightsColumn::DataType>();
upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX];
float* alteredHeightPtr if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2))
= paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); upAlteredHeight
upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2);
}
} }
if (inCellX > 0) if (inCellX > 0)
{ {
leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY);
leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
if (inCellY > 0) if (inCellY > 0)
{ {
upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1);
upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
if (inCellX == ESM::Land::LAND_SIZE - 1) if (inCellX == ESM::Land::LAND_SIZE - 1)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
if (isLandLoaded(cellId))
{
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer const CSMWorld::LandHeightsColumn::DataType landRightShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>(); .value<CSMWorld::LandHeightsColumn::DataType>();
rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); if (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) if (inCellY == ESM::Land::LAND_SIZE - 1)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
if (isLandLoaded(cellId))
{
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer const CSMWorld::LandHeightsColumn::DataType landDownShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>(); .value<CSMWorld::LandHeightsColumn::DataType>();
downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); if (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) if (inCellX < ESM::Land::LAND_SIZE - 1)
{ {
rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY))
rightAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY);
} }
if (inCellY < ESM::Land::LAND_SIZE - 1) if (inCellY < ESM::Land::LAND_SIZE - 1)
{ {
downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1))
downAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f; downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1);
} }
float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight

View file

@ -1,5 +1,4 @@
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -8,9 +7,5 @@ int main(int argc, char* argv[])
Log::sMinDebugLevel = Debug::getDebugLevel(); Log::sMinDebugLevel = Debug::getDebugLevel();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -373,15 +373,11 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mScriptConsoleMode(false) , mScriptConsoleMode(false)
, mActivationDistanceOverride(-1) , mActivationDistanceOverride(-1)
, mGrab(true) , mGrab(true)
, mExportFonts(false)
, mRandomSeed(0) , mRandomSeed(0)
, mNewGame(false) , mNewGame(false)
, mCfgMgr(configurationManager) , mCfgMgr(configurationManager)
, mGlMaxTextureImageUnits(0) , 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 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
Uint32 flags Uint32 flags
@ -811,7 +807,7 @@ void OMW::Engine::prepareEngine()
rootNode->addChild(guiRoot); rootNode->addChild(guiRoot);
mWindowManager = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWindowManager = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(),
mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding,
Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr); Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr);
mEnvironment.setWindowManager(*mWindowManager); mEnvironment.setWindowManager(*mWindowManager);
@ -851,7 +847,7 @@ void OMW::Engine::prepareEngine()
} }
listener->loadingOff(); 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()); mEnvironment.setWorldScene(mWorld->getWorldScene());
mWorld->setupPlayer(); mWorld->setupPlayer();
mWorld->setRandomSeed(mRandomSeed); mWorld->setRandomSeed(mRandomSeed);
@ -1113,11 +1109,6 @@ void OMW::Engine::setWarningsMode(int mode)
mWarningsMode = mode; mWarningsMode = mode;
} }
void OMW::Engine::enableFontExport(bool exportFonts)
{
mExportFonts = exportFonts;
}
void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame) void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame)
{ {
mSaveGameFile = savegame; mSaveGameFile = savegame;

View file

@ -4,7 +4,6 @@
#include <filesystem> #include <filesystem>
#include <components/compiler/extensions.hpp> #include <components/compiler/extensions.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/files/collections.hpp> #include <components/files/collections.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -172,9 +171,7 @@ namespace OMW
// Grab mouse? // Grab mouse?
bool mGrab; bool mGrab;
bool mExportFonts;
unsigned int mRandomSeed; unsigned int mRandomSeed;
Debug::Level mMaxRecastLogLevel = Debug::Error;
Compiler::Extensions mExtensions; Compiler::Extensions mExtensions;
std::unique_ptr<Compiler::Context> mScriptContext; std::unique_ptr<Compiler::Context> mScriptContext;
@ -183,9 +180,6 @@ namespace OMW
Translation::Storage mTranslationDataStorage; Translation::Storage mTranslationDataStorage;
bool mNewGame; bool mNewGame;
Files::ConfigurationManager& mCfgMgr;
int mGlMaxTextureImageUnits;
// not implemented // not implemented
Engine(const Engine&); Engine(const Engine&);
Engine& operator=(const Engine&); Engine& operator=(const Engine&);
@ -257,14 +251,14 @@ namespace OMW
void setWarningsMode(int mode); void setWarningsMode(int mode);
void enableFontExport(bool exportFonts);
/// Set the save game file to load after initialising the engine. /// Set the save game file to load after initialising the engine.
void setSaveGameFile(const std::filesystem::path& savegame); void setSaveGameFile(const std::filesystem::path& savegame);
void setRandomSeed(unsigned int seed); void setRandomSeed(unsigned int seed);
void setRecastMaxLogLevel(Debug::Level value) { mMaxRecastLogLevel = value; } private:
Files::ConfigurationManager& mCfgMgr;
int mGlMaxTextureImageUnits;
}; };
} }

View file

@ -28,6 +28,8 @@ extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x
#include <unistd.h> #include <unistd.h>
#endif #endif
using namespace Fallback;
/** /**
* \brief Parses application command line and calls \ref Cfg::ConfigurationManager * \brief Parses application command line and calls \ref Cfg::ConfigurationManager
* to parse configuration files. * 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<Files::MaybeQuotedPath>().u8string()); engine.setSaveGameFile(variables["load-savegame"].as<Files::MaybeQuotedPath>().u8string());
// other settings // other settings
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
engine.setSoundUsage(!variables["no-sound"].as<bool>()); engine.setSoundUsage(!variables["no-sound"].as<bool>());
engine.setActivationDistanceOverride(variables["activate-dist"].as<int>()); engine.setActivationDistanceOverride(variables["activate-dist"].as<int>());
engine.enableFontExport(variables["export-fonts"].as<bool>());
engine.setRandomSeed(variables["random-seed"].as<unsigned int>()); engine.setRandomSeed(variables["random-seed"].as<unsigned int>());
return true; return true;
@ -219,8 +220,6 @@ int runApplication(int argc, char* argv[])
Files::ConfigurationManager cfgMgr; Files::ConfigurationManager cfgMgr;
std::unique_ptr<OMW::Engine> engine = std::make_unique<OMW::Engine>(cfgMgr); std::unique_ptr<OMW::Engine> engine = std::make_unique<OMW::Engine>(cfgMgr);
engine->setRecastMaxLogLevel(Debug::getRecastMaxLogLevel());
if (parseOptions(argc, argv, *engine, cfgMgr)) if (parseOptions(argc, argv, *engine, cfgMgr))
{ {
if (!Misc::checkRequiredOSGPluginsArePresent()) if (!Misc::checkRequiredOSGPluginsArePresent())

View file

@ -200,7 +200,7 @@ namespace MWBase
///< Skip the animation for the given MW-reference for one frame. Calls to this function for ///< 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. /// 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; virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;

View file

@ -363,7 +363,7 @@ namespace MWBase
void windowVisibilityChange(bool visible) override = 0; void windowVisibilityChange(bool visible) override = 0;
void windowResized(int x, int y) override = 0; void windowResized(int x, int y) override = 0;
void windowClosed() override = 0; void windowClosed() override = 0;
virtual bool isWindowVisible() const = 0; virtual bool isWindowVisible() = 0;
virtual void watchActor(const MWWorld::Ptr& ptr) = 0; virtual void watchActor(const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr getWatchedActor() const = 0; virtual MWWorld::Ptr getWatchedActor() const = 0;

View file

@ -237,12 +237,7 @@ namespace MWClass
bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const
{ {
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
{ return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems();
if (!canBeHarvested(ptr))
return true;
const MWWorld::ContainerStore& store = data->asContainerCustomData().mStore;
return !store.isResolved() || store.hasVisibleItems();
}
return true; return true;
} }

View file

@ -236,8 +236,11 @@ namespace MWClass
} }
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
float dist = store.find("fCombatDistance")->mValue.getFloat();
if (!weapon.isEmpty())
dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist); const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
if (result.first.isEmpty()) // Didn't hit anything if (result.first.isEmpty()) // Didn't hit anything
return true; return true;
@ -278,9 +281,6 @@ namespace MWClass
if (otherstats.isDead()) // Can't hit dead actors if (otherstats.isDead()) // Can't hit dead actors
return; return;
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
return;
if (!success) if (!success)
{ {
victim.getClass().onHit( victim.getClass().onHit(

View file

@ -572,8 +572,12 @@ namespace MWClass
weapon = *weaponslot; weapon = *weaponslot;
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
float dist = fCombatDistance
* (!weapon.isEmpty() ? weapon.get<ESM::Weapon>()->mBase->mData.mReach
: store.find("fHandToHandReach")->mValue.getFloat());
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist); const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
if (result.first.isEmpty()) // Didn't hit anything if (result.first.isEmpty()) // Didn't hit anything
return true; return true;
@ -611,9 +615,6 @@ namespace MWClass
if (otherstats.isDead()) // Can't hit dead actors if (otherstats.isDead()) // Can't hit dead actors
return; return;
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
return;
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->setEnemy(victim); MWBase::Environment::get().getWindowManager()->setEnemy(victim);

View file

@ -1343,13 +1343,12 @@ namespace MWGui
return codePoint == '\r'; 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) static bool ucsSpace(int codePoint)
{ {
switch (codePoint) switch (codePoint)
{ {
case 0x0020: // SPACE case 0x0020: // SPACE
case 0x00A0: // NO-BREAK SPACE
case 0x1680: // OGHAM SPACE MARK case 0x1680: // OGHAM SPACE MARK
case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x180E: // MONGOLIAN VOWEL SEPARATOR
case 0x2000: // EN QUAD 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) static bool ucsBreakingSpace(int codePoint)
{ {
switch (codePoint) switch (codePoint)
{ {
case 0x0020: // SPACE case 0x0020: // SPACE
// case 0x00A0: // NO-BREAK SPACE
case 0x1680: // OGHAM SPACE MARK case 0x1680: // OGHAM SPACE MARK
case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x180E: // MONGOLIAN VOWEL SEPARATOR
case 0x2000: // EN QUAD case 0x2000: // EN QUAD
@ -1391,12 +1388,15 @@ namespace MWGui
case 0x2004: // THREE-PER-EM SPACE case 0x2004: // THREE-PER-EM SPACE
case 0x2005: // FOUR-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE
case 0x2006: // SIX-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE
case 0x2007: // FIGURE SPACE
case 0x2008: // PUNCTUATION SPACE case 0x2008: // PUNCTUATION SPACE
case 0x2009: // THIN SPACE case 0x2009: // THIN SPACE
case 0x200A: // HAIR SPACE case 0x200A: // HAIR SPACE
case 0x200B: // ZERO WIDTH SPACE case 0x200B: // ZERO WIDTH SPACE
case 0x202F: // NARROW NO-BREAK SPACE
case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE
case 0x3000: // IDEOGRAPHIC SPACE case 0x3000: // IDEOGRAPHIC SPACE
// case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
return true; return true;
default: default:
return false; return false;

View file

@ -666,8 +666,7 @@ namespace MWGui
else if (scrollbar) else if (scrollbar)
{ {
mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second));
// Scroll range should be >= 2 to enable scrolling and prevent a crash size_t range = book->getSize().second - viewHeight;
size_t range = std::max(book->getSize().second - viewHeight, size_t(2));
mScrollBar->setScrollRange(range); mScrollBar->setScrollRange(range);
mScrollBar->setScrollPosition(range - 1); mScrollBar->setScrollPosition(range - 1);
mScrollBar->setTrackSize( mScrollBar->setTrackSize(

View file

@ -29,26 +29,11 @@ namespace MWGui
{ {
Misc::FrameRateLimiter frameRateLimiter Misc::FrameRateLimiter frameRateLimiter
= Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit());
const MWBase::WindowManager& windowManager = *MWBase::Environment::get().getWindowManager();
bool paused = false;
while (mRunning) while (mRunning)
{ {
if (windowManager.isWindowVisible())
{
if (paused)
{
mVideo->resume();
paused = false;
}
// If finished playing, start again // If finished playing, start again
if (!mVideo->update()) if (!mVideo->update())
mVideo->playVideo("video\\menu_background.bik"); mVideo->playVideo("video\\menu_background.bik");
}
else if (!paused)
{
paused = true;
mVideo->pause();
}
frameRateLimiter.limit(); frameRateLimiter.limit();
} }
} }

View file

@ -146,7 +146,7 @@ namespace MWGui
WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath,
bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, 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) : mOldUpdateMask(0)
, mOldCullMask(0) , mOldCullMask(0)
, mStore(nullptr) , mStore(nullptr)
@ -215,8 +215,7 @@ namespace MWGui
MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag);
// Load fonts // Load fonts
mFontLoader mFontLoader = std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor);
= std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor, exportFonts);
// Register own widgets with MyGUI // Register own widgets with MyGUI
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
@ -730,9 +729,6 @@ namespace MWGui
return; return;
} }
if (mGuiModes.empty())
return;
GuiModeState& state = mGuiModeStates[mGuiModes.back()]; GuiModeState& state = mGuiModeStates[mGuiModes.back()];
for (const auto& window : state.mWindows) 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 // TODO: check if any windows are now off-screen and move them back if so
} }
bool WindowManager::isWindowVisible() const bool WindowManager::isWindowVisible()
{ {
return mWindowVisible; return mWindowVisible;
} }

View file

@ -128,7 +128,7 @@ namespace MWGui
WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, 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); Files::ConfigurationManager& cfgMgr);
virtual ~WindowManager(); virtual ~WindowManager();
@ -290,7 +290,7 @@ namespace MWGui
void windowVisibilityChange(bool visible) override; void windowVisibilityChange(bool visible) override;
void windowResized(int x, int y) override; void windowResized(int x, int y) override;
void windowClosed() override; void windowClosed() override;
bool isWindowVisible() const override; bool isWindowVisible() override;
void watchActor(const MWWorld::Ptr& ptr) override; void watchActor(const MWWorld::Ptr& ptr) override;
MWWorld::Ptr getWatchedActor() const override; MWWorld::Ptr getWatchedActor() const override;

View file

@ -148,7 +148,7 @@ namespace MWLua
}; };
} }
sol::table readOnlyApi = LuaUtil::makeReadOnly(api); sol::table readOnly = LuaUtil::makeReadOnly(api);
return context.setTypePackage(readOnlyApi, "openmw_core"); return context.setTypePackage(readOnly, "openmw_core");
} }
} }

View file

@ -38,12 +38,12 @@ namespace MWLua
sol::table initInputPackage(const Context& context) 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(); sol::state_view lua = context.sol();
{
if (lua["openmw_input"] != sol::nil)
return lua["openmw_input"];
}
context.cachePackage("openmw_input_keyevent", [&lua]() {
sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent"); sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent");
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) {
if (e.sym > 0 && e.sym <= 255) if (e.sym > 0 && e.sym <= 255)
@ -52,39 +52,28 @@ namespace MWLua
return std::string(); return std::string();
}); });
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; }); keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
keyEvent["withShift"] keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
= 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["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["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
keyEvent["withSuper"] keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
return sol::table(lua, sol::create);
});
context.cachePackage("openmw_input_touchpadevent", [&lua]() {
auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent"); auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent");
touchpadEvent["device"] touchpadEvent["device"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; });
= 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["finger"]
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; });
touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f { touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f {
return { e.mX, e.mY }; return { e.mX, e.mY };
}); });
touchpadEvent["pressure"] touchpadEvent["pressure"]
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
return sol::table(lua, sol::create);
});
context.cachePackage("openmw_input_inputactions", [&lua]() {
auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions"); auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions");
inputActions[sol::meta_function::index] inputActions[sol::meta_function::index]
= [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; };
{ {
auto pairs = [](LuaUtil::InputAction::Registry& registry) { auto pairs = [](LuaUtil::InputAction::Registry& registry) {
auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key) auto next
-> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> { = [](LuaUtil::InputAction::Registry& registry,
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> {
std::optional<std::string> nextKey(registry.nextKey(key)); std::optional<std::string> nextKey(registry.nextKey(key));
if (!nextKey.has_value()) if (!nextKey.has_value())
return sol::nullopt; return sol::nullopt;
@ -95,10 +84,7 @@ namespace MWLua
}; };
inputActions[sol::meta_function::pairs] = pairs; inputActions[sol::meta_function::pairs] = pairs;
} }
return sol::table(lua, sol::create);
});
context.cachePackage("openmw_input_actioninfo", [&lua]() {
auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo"); auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo");
actionInfo["key"] = sol::readonly_property( actionInfo["key"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; });
@ -108,21 +94,18 @@ namespace MWLua
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; });
actionInfo["l10n"] = sol::readonly_property( actionInfo["l10n"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; });
actionInfo["type"] actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
actionInfo["defaultValue"] actionInfo["defaultValue"]
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; });
return sol::table(lua, sol::create);
});
context.cachePackage("openmw_input_inputtriggers", [&lua]() {
auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers"); auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
inputTriggers[sol::meta_function::index] inputTriggers[sol::meta_function::index]
= [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; };
{ {
auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { auto pairs = [](LuaUtil::InputTrigger::Registry& registry) {
auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) auto next
-> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> { = [](LuaUtil::InputTrigger::Registry& registry,
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> {
std::optional<std::string> nextKey(registry.nextKey(key)); std::optional<std::string> nextKey(registry.nextKey(key));
if (!nextKey.has_value()) if (!nextKey.has_value())
return sol::nullopt; return sol::nullopt;
@ -133,10 +116,7 @@ namespace MWLua
}; };
inputTriggers[sol::meta_function::pairs] = pairs; inputTriggers[sol::meta_function::pairs] = pairs;
} }
return sol::table(lua, sol::create);
});
context.cachePackage("openmw_input_triggerinfo", [&lua]() {
auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo"); auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo");
triggerInfo["key"] = sol::readonly_property( triggerInfo["key"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; });
@ -146,8 +126,6 @@ namespace MWLua
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; });
triggerInfo["l10n"] = sol::readonly_property( triggerInfo["l10n"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; });
return sol::table(lua, sol::create);
});
MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
sol::table api(lua, sol::create); sol::table api(lua, sol::create);
@ -161,8 +139,7 @@ namespace MWLua
})); }));
api["actions"] = std::ref(context.mLuaManager->inputActions()); api["actions"] = std::ref(context.mLuaManager->inputActions());
api["registerAction"] api["registerAction"] = [manager = context.mLuaManager](sol::table options) {
= [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
LuaUtil::InputAction::Info parsedOptions; LuaUtil::InputAction::Info parsedOptions;
parsedOptions.mKey = options["key"].get<std::string_view>(); parsedOptions.mKey = options["key"].get<std::string_view>();
parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>(); parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>();
@ -170,7 +147,6 @@ namespace MWLua
parsedOptions.mName = options["name"].get<std::string_view>(); parsedOptions.mName = options["name"].get<std::string_view>();
parsedOptions.mDescription = options["description"].get<std::string_view>(); parsedOptions.mDescription = options["description"].get<std::string_view>();
parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>(); parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>();
parsedOptions.mPersistent = persistent;
manager->inputActions().insert(std::move(parsedOptions)); manager->inputActions().insert(std::move(parsedOptions));
}; };
api["bindAction"] = [manager = context.mLuaManager]( api["bindAction"] = [manager = context.mLuaManager](
@ -202,14 +178,12 @@ namespace MWLua
}; };
api["triggers"] = std::ref(context.mLuaManager->inputTriggers()); api["triggers"] = std::ref(context.mLuaManager->inputTriggers());
api["registerTrigger"] api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) {
= [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
LuaUtil::InputTrigger::Info parsedOptions; LuaUtil::InputTrigger::Info parsedOptions;
parsedOptions.mKey = options["key"].get<std::string_view>(); parsedOptions.mKey = options["key"].get<std::string_view>();
parsedOptions.mL10n = options["l10n"].get<std::string_view>(); parsedOptions.mL10n = options["l10n"].get<std::string_view>();
parsedOptions.mName = options["name"].get<std::string_view>(); parsedOptions.mName = options["name"].get<std::string_view>();
parsedOptions.mDescription = options["description"].get<std::string_view>(); parsedOptions.mDescription = options["description"].get<std::string_view>();
parsedOptions.mPersistent = persistent;
manager->inputTriggers().insert(std::move(parsedOptions)); manager->inputTriggers().insert(std::move(parsedOptions));
}; };
api["registerTriggerHandler"] api["registerTriggerHandler"]
@ -471,8 +445,8 @@ namespace MWLua
{ "Tab", SDL_SCANCODE_TAB }, { "Tab", SDL_SCANCODE_TAB },
})); }));
sol::table readOnlyApi = LuaUtil::makeReadOnly(api); lua["openmw_input"] = LuaUtil::makeReadOnly(api);
return context.setTypePackage(readOnlyApi, "openmw_input"); return lua["openmw_input"];
} }
} }

View file

@ -71,8 +71,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value;
= sol::main_object(value);
} }
else else
throw std::runtime_error("Only global or self scripts can set the value"); throw std::runtime_error("Only global or self scripts can set the value");

View file

@ -54,7 +54,7 @@ namespace MWLua
{ {
} }
MWBase::LuaManager::ActorControls mControls; MWBase::LuaManager::ActorControls mControls;
std::map<CachedStat, sol::main_object> mStatsCache; std::map<CachedStat, sol::object> mStatsCache;
bool mIsActive; bool mIsActive;
}; };

View file

@ -343,7 +343,6 @@ namespace MWLua
mPlayerStorage.clearTemporaryAndRemoveCallbacks(); mPlayerStorage.clearTemporaryAndRemoveCallbacks();
mInputActions.clear(); mInputActions.clear();
mInputTriggers.clear(); mInputTriggers.clear();
mQueuedAutoStartedScripts.clear();
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0); lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0);
} }
@ -654,8 +653,8 @@ namespace MWLua
MWBase::Environment::get().getL10nManager()->dropCache(); MWBase::Environment::get().getL10nManager()->dropCache();
mUiResourceManager.clear(); mUiResourceManager.clear();
mLua.dropScriptCache(); mLua.dropScriptCache();
mInputActions.clear(true); mInputActions.clear();
mInputTriggers.clear(true); mInputTriggers.clear();
initConfiguration(); initConfiguration();
ESM::LuaScripts globalData; ESM::LuaScripts globalData;

View file

@ -1061,7 +1061,7 @@ namespace MWLua
}; };
// types.Actor.activeEffects(o):removeEffect(id, ?arg) // 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<std::string_view> argStr) { sol::optional<std::string_view> argStr) {
if (!effects.isActor()) if (!effects.isActor())
return; return;
@ -1071,14 +1071,12 @@ namespace MWLua
MWMechanics::EffectKey key = getEffectKey(idStr, argStr); 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 // 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. // spell), we still need to use the active spells store to purge this effect from active spells.
const auto& ptr = effects.mActor.ptr(); const auto& ptr = effects.mActor.ptr();
auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells();
activeSpells.purgeEffect(ptr, key.mId, key.mArg); activeSpells.purgeEffect(ptr, key.mId, key.mArg);
});
}; };
// types.Actor.activeEffects(o):set(value, id, ?arg) // types.Actor.activeEffects(o):set(value, id, ?arg)

View file

@ -213,7 +213,6 @@ namespace MWLua
{ "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound }, { "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound },
{ "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound }, { "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound },
{ "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound }, { "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound },
{ "TargetPolygonNotFound", DetourNavigator::Status::TargetPolygonNotFound },
{ "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed }, { "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed },
{ "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed }, { "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed },
{ "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed }, { "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed },

View file

@ -123,8 +123,7 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value;
= sol::main_object(value);
} }
}; };
@ -160,7 +159,7 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }]
= sol::main_object(value); = value;
} }
}; };
@ -184,8 +183,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value;
= sol::main_object(value);
} }
sol::object getProgress(const Context& context) const sol::object getProgress(const Context& context) const
@ -206,8 +204,7 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value;
= sol::main_object(value);
} }
SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const
@ -261,7 +258,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); 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) 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(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); 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) 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(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); 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) 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(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast<int>(mIndex), prop }] obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast<int>(mIndex), prop }] = value;
= sol::main_object(value);
} }
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)

View file

@ -43,7 +43,7 @@ namespace MWLua
[](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); }); [](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); });
record["chanceNone"] = sol::readonly_property( record["chanceNone"] = sol::readonly_property(
[](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); }); [](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); sol::table res(state, sol::create);
for (size_t i = 0; i < rec.mList.size(); ++i) for (size_t i = 0; i < rec.mList.size(); ++i)
res[LuaUtil::toLuaIndex(i)] = rec.mList[i]; res[LuaUtil::toLuaIndex(i)] = rec.mList[i];

View file

@ -63,7 +63,7 @@ namespace
if (rec["type"] != sol::nil) if (rec["type"] != sol::nil)
{ {
int weaponType = rec["type"].get<int>(); int weaponType = rec["type"].get<int>();
if (weaponType >= 0 && weaponType <= ESM::Weapon::Last) if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown)
weapon.mData.mType = weaponType; weapon.mData.mType = weaponType;
else else
throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType));

View file

@ -168,8 +168,7 @@ namespace MWLua
if (index == LuaUi::Layer::count()) if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found")); throw std::logic_error(std::string("Layer not found"));
index++; index++;
context.mLuaManager->addAction( context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
}; };
layersTable["insertBefore"] = [context]( layersTable["insertBefore"] = [context](
std::string_view beforename, std::string_view name, const sol::object& opt) { 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); size_t index = LuaUi::Layer::indexOf(beforename);
if (index == LuaUi::Layer::count()) if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found")); throw std::logic_error(std::string("Layer not found"));
context.mLuaManager->addAction( context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
}; };
sol::table layers = LuaUtil::makeReadOnly(layersTable); sol::table layers = LuaUtil::makeReadOnly(layersTable);
sol::table layersMeta = layers[sol::metatable_key]; sol::table layersMeta = layers[sol::metatable_key];
@ -287,9 +285,8 @@ namespace MWLua
return res; return res;
}; };
api["_setWindowDisabled"] api["_setWindowDisabled"]
= [windowManager, luaManager = context.mLuaManager](std::string window, bool disabled) { = [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) {
luaManager->addAction( luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); });
[=, window = std::move(window)]() { windowManager->setDisabledByLua(window, disabled); });
}; };
// TODO // TODO
@ -311,7 +308,7 @@ namespace MWLua
return res.str(); return res.str();
}; };
element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, 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<LuaUi::Element>& element) { element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mState != LuaUi::Element::Created) if (element->mState != LuaUi::Element::Created)
return; return;

View file

@ -68,7 +68,7 @@ namespace MWLua
Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'."; 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; std::ostringstream os;
if (file.mFilePtr && file.mFilePtr->peek() != EOF) if (file.mFilePtr && file.mFilePtr->peek() != EOF)
@ -79,7 +79,7 @@ namespace MWLua
return sol::make_object<std::string>(lua, std::move(result)); return sol::make_object<std::string>(lua, std::move(result));
} }
sol::object readLineFromFile(lua_State* lua, FileHandle& file) sol::object readLineFromFile(sol::this_state lua, FileHandle& file)
{ {
std::string result; std::string result;
if (file.mFilePtr && std::getline(*file.mFilePtr, result)) if (file.mFilePtr && std::getline(*file.mFilePtr, result))
@ -91,7 +91,7 @@ namespace MWLua
return sol::nil; 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; double number = 0;
if (file && *file >> number) if (file && *file >> number)
@ -100,7 +100,7 @@ namespace MWLua
return sol::nil; 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) if (count <= 0 && file.mFilePtr->peek() != EOF)
return sol::make_object<std::string>(lua, std::string()); return sol::make_object<std::string>(lua, std::string());
@ -189,7 +189,7 @@ namespace MWLua
return seek(lua, self, std::ios_base::cur, off); 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<FileHandle*>()) if (!self.is<FileHandle*>())
throw std::runtime_error("self should be a file handle"); throw std::runtime_error("self should be a file handle");
return sol::as_function([lua, self]() -> sol::object { 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); auto normalizedName = VFS::Path::normalizeFilename(fileName);
return sol::as_function( return sol::as_function(
[lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable { [lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable {

View file

@ -289,9 +289,8 @@ namespace MWMechanics
const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot); const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot);
if (enchantmentId.empty()) if (enchantmentId.empty())
continue; continue;
const ESM::Enchantment* enchantment const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
= world->getStore().get<ESM::Enchantment>().search(enchantmentId); if (enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
if (enchantment == nullptr || enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
continue; continue;
if (std::find_if(mSpells.begin(), mSpells.end(), if (std::find_if(mSpells.begin(), mSpells.end(),
[&](const ActiveSpellParams& params) { [&](const ActiveSpellParams& params) {

View file

@ -2028,7 +2028,7 @@ namespace MWMechanics
iter->second->getCharacterController().skipAnim(); 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); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())

View file

@ -119,7 +119,7 @@ namespace MWMechanics
std::string_view startKey, std::string_view stopKey, bool forceLoop); std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr) const; 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; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
void persistAnimationStates() const; void persistAnimationStates() const;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted); void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted);

View file

@ -40,15 +40,15 @@ namespace MWMechanics
static const std::size_t MAX_IDLE_SIZE = 8; static const std::size_t MAX_IDLE_SIZE = 8;
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
"idle2", std::string("idle2"),
"idle3", std::string("idle3"),
"idle4", std::string("idle4"),
"idle5", std::string("idle5"),
"idle6", std::string("idle6"),
"idle7", std::string("idle7"),
"idle8", std::string("idle8"),
"idle9", std::string("idle9"),
}; };
namespace namespace
@ -680,7 +680,7 @@ namespace MWMechanics
{ {
if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) 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); return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1);
} }
else else
@ -695,7 +695,7 @@ namespace MWMechanics
{ {
if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) 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); return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName);
} }
else else

View file

@ -3,7 +3,6 @@
#include "typedaipackage.hpp" #include "typedaipackage.hpp"
#include <string_view>
#include <vector> #include <vector>
#include "aitemporarybase.hpp" #include "aitemporarybase.hpp"
@ -182,7 +181,9 @@ namespace MWMechanics
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
/// lookup table for converting idleSelect value to groupName /// 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();
}; };
} }

View file

@ -1104,10 +1104,10 @@ namespace MWMechanics
attackType = ESM::Weapon::AT_Thrust; attackType = ESM::Weapon::AT_Thrust;
// We want to avoid hit keys that come out of nowhere (e.g. in the follow animation) // 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 // and processing multiple hit keys for a single attack
if (mReadyToHit) if (mAttackStrength != -1.f)
{ {
charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess); charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess);
mReadyToHit = false; mAttackStrength = -1.f;
} }
} }
else if (isRandomAttackAnimation(groupname) && action == "start") else if (isRandomAttackAnimation(groupname) && action == "start")
@ -1153,10 +1153,10 @@ namespace MWMechanics
else if (action == "shoot release") else if (action == "shoot release")
{ {
// See notes for melee release above // See notes for melee release above
if (mReadyToHit) if (mAttackStrength != -1.f)
{ {
mAnimation->releaseArrow(mAttackStrength); mAnimation->releaseArrow(mAttackStrength);
mReadyToHit = false; mAttackStrength = -1.f;
} }
} }
else if (action == "shoot follow attach") else if (action == "shoot follow attach")
@ -1246,7 +1246,7 @@ namespace MWMechanics
void CharacterController::prepareHit() void CharacterController::prepareHit()
{ {
if (mReadyToHit) if (mAttackStrength != -1.f)
return; return;
auto& prng = MWBase::Environment::get().getWorld()->getPrng(); auto& prng = MWBase::Environment::get().getWorld()->getPrng();
@ -1261,8 +1261,6 @@ namespace MWMechanics
mAttackStrength = 0.f; mAttackStrength = 0.f;
playSwishSound(); playSwishSound();
} }
mReadyToHit = true;
} }
bool CharacterController::updateWeaponState() bool CharacterController::updateWeaponState()
@ -1522,7 +1520,6 @@ namespace MWMechanics
&& (mHitState == CharState_None || mHitState == CharState_Block)) && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
mAttackStrength = -1.f; mAttackStrength = -1.f;
mReadyToHit = false;
// Randomize attacks for non-bipedal creatures // Randomize attacks for non-bipedal creatures
if (!cls.isBipedal(mPtr) if (!cls.isBipedal(mPtr)
@ -1809,7 +1806,8 @@ namespace MWMechanics
stop = strength + ' ' + stop; 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) if (animPlaying)
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);

View file

@ -172,7 +172,6 @@ namespace MWMechanics
std::string mCurrentWeapon; std::string mCurrentWeapon;
float mAttackStrength{ -1.f }; float mAttackStrength{ -1.f };
bool mReadyToHit{ false };
MWWorld::Ptr mAttackVictim; MWWorld::Ptr mAttackVictim;
osg::Vec3f mAttackHitPos; osg::Vec3f mAttackHitPos;
bool mAttackSuccess{ false }; bool mAttackSuccess{ false };

View file

@ -578,24 +578,6 @@ namespace MWMechanics
return dist; return dist;
} }
float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
if (!weapon.isEmpty())
return fCombatDistance * weapon.get<ESM::Weapon>()->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<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach) std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach)
{ {
// Lasciate ogne speranza, voi ch'entrate // Lasciate ogne speranza, voi ch'entrate
@ -632,12 +614,10 @@ namespace MWMechanics
{ {
if (actor == target || target.getClass().getCreatureStats(target).isDead()) if (actor == target || target.getClass().getCreatureStats(target).isDead())
continue; continue;
const float dist = getDistanceToBounds(actor, target); const float dist = getDistanceToBounds(actor, target);
if (dist >= minDist || !isInMeleeReach(actor, target, reach))
continue;
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach)
continue;
// Horizontal angle checks. // Horizontal angle checks.
osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() };

View file

@ -64,10 +64,6 @@ namespace MWMechanics
// Cursed distance calculation used for combat proximity and hit checks in Morrowind // Cursed distance calculation used for combat proximity and hit checks in Morrowind
float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); 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 // Similarly cursed hit target selection
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach); std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach);

View file

@ -778,8 +778,7 @@ namespace MWMechanics
else else
mObjects.skipAnimation(ptr); mObjects.skipAnimation(ptr);
} }
bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName)
bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
return mActors.checkAnimationPlaying(ptr, groupName); return mActors.checkAnimationPlaying(ptr, groupName);

View file

@ -146,7 +146,7 @@ namespace MWMechanics
std::string_view startKey, std::string_view stopKey, bool forceLoop) override; std::string_view startKey, std::string_view stopKey, bool forceLoop) override;
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
void skipAnimation(const MWWorld::Ptr& ptr) 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; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
void persistAnimationStates() override; void persistAnimationStates() override;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override; void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override;

View file

@ -161,7 +161,7 @@ namespace MWRender
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
{ {
if (Settings::game().mShieldSheathing && mObjectRoot) if (Settings::game().mShieldSheathing)
{ {
const MWWorld::Class& cls = mPtr.getClass(); const MWWorld::Class& cls = mPtr.getClass();
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr); MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);

View file

@ -1695,7 +1695,7 @@ namespace MWRender
mGlowUpdater->setColor(color); mGlowUpdater->setColor(color);
mGlowUpdater->setDuration(glowDuration); mGlowUpdater->setDuration(glowDuration);
} }
else if (mObjectRoot) else
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration);
} }
} }
@ -1869,7 +1869,7 @@ namespace MWRender
void Animation::setAlpha(float alpha) void Animation::setAlpha(float alpha)
{ {
if (alpha == mAlpha || !mObjectRoot) if (alpha == mAlpha)
return; return;
mAlpha = alpha; mAlpha = alpha;

View file

@ -260,6 +260,7 @@ namespace MWRender
// turn off sky blending // turn off sky blending
stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("far", 10000000.0f));
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.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("screenRes", osg::Vec2f{ 1, 1 }));
stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f));

View file

@ -259,7 +259,6 @@ namespace MWRender
void CreatureWeaponAnimation::addControllers() void CreatureWeaponAnimation::addControllers()
{ {
Animation::addControllers(); Animation::addControllers();
if (mObjectRoot)
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
} }

View file

@ -736,6 +736,7 @@ namespace MWRender
// turn of sky blending // turn of sky blending
stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("far", 10000000.0f));
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.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("screenRes", osg::Vec2f{ 1, 1 }));
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel; osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;

View file

@ -762,18 +762,14 @@ namespace MWRender
if (Misc::StringUtils::ciEqual(technique->getName(), name)) if (Misc::StringUtils::ciEqual(technique->getName(), name))
return technique; return technique;
std::string realName = name;
auto fileIter = mTechniqueFileMap.find(name);
if (fileIter != mTechniqueFileMap.end())
realName = fileIter->first;
auto technique = std::make_shared<fx::Technique>(*mVFS, *mRendering.getResourceSystem()->getImageManager(), auto technique = std::make_shared<fx::Technique>(*mVFS, *mRendering.getResourceSystem()->getImageManager(),
std::move(realName), renderWidth(), renderHeight(), mUBO, mNormalsSupported); name, renderWidth(), renderHeight(), mUBO, mNormalsSupported);
technique->compile(); technique->compile();
if (technique->getStatus() != fx::Technique::Status::File_Not_exists) 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) if (loadNextFrame)
{ {

View file

@ -18,7 +18,6 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/fx/stateupdater.hpp> #include <components/fx/stateupdater.hpp>
#include <components/fx/technique.hpp> #include <components/fx/technique.hpp>
#include <components/misc/strings/algorithm.hpp>
#include "pingpongcanvas.hpp" #include "pingpongcanvas.hpp"
#include "transparentpass.hpp" #include "transparentpass.hpp"
@ -230,8 +229,7 @@ namespace MWRender
TechniqueList mQueuedTemplates; TechniqueList mQueuedTemplates;
TechniqueList mInternalTechniques; TechniqueList mInternalTechniques;
std::unordered_map<std::string, std::filesystem::path, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap;
mTechniqueFileMap;
RenderingManager& mRendering; RenderingManager& mRendering;
osgViewer::Viewer* mViewer; osgViewer::Viewer* mViewer;

View file

@ -49,6 +49,11 @@ namespace MWRender
&& exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute;
#endif #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) for (size_t i = 0; i < mState.size(); ++i)
{ {
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet; osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
@ -86,18 +91,6 @@ namespace MWRender
else else
setupFragmentPipeline(); 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); setCullCallback(new osg::NodeCallback);
setUpdateCallback(new osg::NodeCallback); setUpdateCallback(new osg::NodeCallback);
} }
@ -109,36 +102,21 @@ namespace MWRender
Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } };
osg::ref_ptr<osg::Shader> vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); osg::ref_ptr<osg::Shader> vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> blobber
= shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT);
osg::ref_ptr<osg::Shader> 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)); mProgramBlobber = shaderManager.getProgram(
mProgramSimulation = shaderManager.getProgram(std::move(vertex), std::move(simulate)); 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() void RipplesSurface::setupComputePipeline()
{ {
auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager();
osg::ref_ptr<osg::Shader> blobber mProgramBlobber = shaderManager.getProgram(
= shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE); nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE));
osg::ref_ptr<osg::Shader> simulate mProgramSimulation = shaderManager.getProgram(
= shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE); nullptr, 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));
} }
void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state)
@ -206,9 +184,6 @@ namespace MWRender
void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const
{ {
if (mProgramBlobber == nullptr || mProgramSimulation == nullptr)
return;
osg::State& state = *renderInfo.getState(); osg::State& state = *renderInfo.getState();
const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2;
const State& frameState = mState[currentFrame]; const State& frameState = mState[currentFrame];

View file

@ -262,15 +262,15 @@ namespace MWRender
, mPrecipitationAlpha(0.f) , mPrecipitationAlpha(0.f)
, mDirtyParticlesEffect(false) , mDirtyParticlesEffect(false)
{ {
mSkyRootNode = new CameraRelativeTransform; osg::ref_ptr<CameraRelativeTransform> skyroot = new CameraRelativeTransform;
mSkyRootNode->setName("Sky Root"); skyroot->setName("Sky Root");
// Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline
if (!mSceneManager->getForceShaders()) if (!mSceneManager->getForceShaders())
mSkyRootNode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),
osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON);
mSceneManager->setUpNormalsRTForStateSet(mSkyRootNode->getOrCreateStateSet(), false); mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false);
SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*mSkyRootNode->getOrCreateStateSet()); SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet());
parentNode->addChild(mSkyRootNode); parentNode->addChild(skyroot);
mEarlyRenderBinRoot = new osg::Group; mEarlyRenderBinRoot = new osg::Group;
// render before the world is rendered // render before the world is rendered
@ -281,18 +281,19 @@ namespace MWRender
if (enableSkyRTT) if (enableSkyRTT)
{ {
mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot); 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; mRootNode->setNodeMask(Mask_Sky);
mSkyNode->setNodeMask(Mask_Sky); mRootNode->addChild(mEarlyRenderBinRoot);
mSkyNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot);
mSkyRootNode->addChild(mSkyNode);
mUnderwaterSwitch = new UnderwaterSwitchCallback(mSkyRootNode);
mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion; mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion;
mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(mSkyRootNode, parentNode, rootNode, camera); mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(skyroot, parentNode, rootNode, camera);
} }
void SkyManager::create() void SkyManager::create()
@ -463,7 +464,7 @@ namespace MWRender
mRainParticleSystem->setUserValue("particleOcclusion", true); mRainParticleSystem->setUserValue("particleOcclusion", true);
mSceneManager->recreateShaders(mRainNode); mSceneManager->recreateShaders(mRainNode);
mSkyNode->addChild(mRainNode); mRootNode->addChild(mRainNode);
if (mPrecipitationOcclusion) if (mPrecipitationOcclusion)
mPrecipitationOccluder->enable(); mPrecipitationOccluder->enable();
} }
@ -473,7 +474,7 @@ namespace MWRender
if (!mRainNode) if (!mRainNode)
return; return;
mSkyNode->removeChild(mRainNode); mRootNode->removeChild(mRainNode);
mRainNode = nullptr; mRainNode = nullptr;
mPlacer = nullptr; mPlacer = nullptr;
mCounter = nullptr; mCounter = nullptr;
@ -484,10 +485,10 @@ namespace MWRender
SkyManager::~SkyManager() SkyManager::~SkyManager()
{ {
if (mSkyRootNode) if (mRootNode)
{ {
mSkyRootNode->getParent(0)->removeChild(mSkyRootNode); mRootNode->getParent(0)->removeChild(mRootNode);
mSkyRootNode = nullptr; mRootNode = nullptr;
} }
} }
@ -594,7 +595,7 @@ namespace MWRender
const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u; const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u;
mEarlyRenderBinRoot->setNodeMask(mask); mEarlyRenderBinRoot->setNodeMask(mask);
mSkyNode->setNodeMask(mask); mRootNode->setNodeMask(mask);
if (!enabled && mParticleNode && mParticleEffect) if (!enabled && mParticleNode && mParticleEffect)
{ {
@ -690,7 +691,7 @@ namespace MWRender
{ {
if (mParticleNode) if (mParticleNode)
{ {
mSkyNode->removeChild(mParticleNode); mRootNode->removeChild(mParticleNode);
mParticleNode = nullptr; mParticleNode = nullptr;
} }
if (mRainEffect.empty()) if (mRainEffect.empty())
@ -705,7 +706,7 @@ namespace MWRender
mParticleNode = new osg::PositionAttitudeTransform; mParticleNode = new osg::PositionAttitudeTransform;
mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->addCullCallback(mUnderwaterSwitch);
mParticleNode->setNodeMask(Mask_WeatherParticles); mParticleNode->setNodeMask(Mask_WeatherParticles);
mSkyNode->addChild(mParticleNode); mRootNode->addChild(mParticleNode);
} }
mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode);

View file

@ -118,8 +118,7 @@ namespace MWRender
osg::Camera* mCamera; osg::Camera* mCamera;
osg::ref_ptr<CameraRelativeTransform> mSkyRootNode; osg::ref_ptr<osg::Group> mRootNode;
osg::ref_ptr<osg::Group> mSkyNode;
osg::ref_ptr<osg::Group> mEarlyRenderBinRoot; osg::ref_ptr<osg::Group> mEarlyRenderBinRoot;
osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode; osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode;

View file

@ -162,17 +162,11 @@ namespace MWScript
public: public:
void execute(Interpreter::Runtime& runtime) override void execute(Interpreter::Runtime& runtime) override
{ {
MWWorld::Ptr ptr = R()(runtime, false); MWWorld::Ptr ptr = R()(runtime);
int index = runtime[0].mInteger; int index = runtime[0].mInteger;
runtime.pop(); runtime.pop();
if (ptr.isEmpty())
{
runtime.push(0);
return;
}
bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying( bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying(
ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index))); ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index)));

View file

@ -417,16 +417,20 @@ namespace MWSound
{ {
{ {
const std::lock_guard<std::mutex> openLock(mOutput.mReopenMutex); const std::lock_guard<std::mutex> openLock(mOutput.mReopenMutex);
std::basic_string_view<ALCchar> defaultName = getDeviceName(nullptr); auto defaultName = getDeviceName(nullptr);
if (mCurrentName != defaultName) if (mCurrentName != defaultName)
{ {
mCurrentName = defaultName; Log(Debug::Info) << "Default audio device changed";
Log(Debug::Info) << "Default audio device changed to \"" << mCurrentName << "\""; ALCboolean reopened
ALCboolean reopened = alcReopenDeviceSOFT( = alcReopenDeviceSOFT(mOutput.mDevice, nullptr, mOutput.mContextAttributes.data());
mOutput.mDevice, mCurrentName.data(), mOutput.mContextAttributes.data());
if (reopened == AL_FALSE) if (reopened == AL_FALSE)
{
mCurrentName = defaultName;
Log(Debug::Warning) << "Failed to switch to new audio device"; Log(Debug::Warning) << "Failed to switch to new audio device";
} }
else
mCurrentName = getDeviceName(mOutput.mDevice);
}
} }
mCondVar.wait_for(lock, std::chrono::seconds(2)); mCondVar.wait_for(lock, std::chrono::seconds(2));
} }

View file

@ -38,7 +38,7 @@ MWState::Character* MWState::CharacterManager::getCurrentCharacter()
return mCurrent; 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<Character>::iterator it = findCharacter(character); std::list<Character>::iterator it = findCharacter(character);
@ -51,7 +51,6 @@ void MWState::CharacterManager::deleteSlot(const MWState::Slot* slot, const MWSt
if (character == mCurrent) if (character == mCurrent)
mCurrent = nullptr; mCurrent = nullptr;
mCharacters.erase(it); mCharacters.erase(it);
character = nullptr;
} }
} }

View file

@ -33,7 +33,7 @@ namespace MWState
Character* getCurrentCharacter(); Character* getCurrentCharacter();
///< @note May return null ///< @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); Character* createCharacter(const std::string& name);
///< Create new character within saved game management ///< Create new character within saved game management

View file

@ -706,10 +706,10 @@ void MWState::StateManager::quickLoad()
void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot)
{ {
const std::filesystem::path savePath = slot->mPath; const std::filesystem::path savePath = slot->mPath;
mCharacterManager.deleteSlot(slot, character); mCharacterManager.deleteSlot(character, slot);
if (mLastSavegame == savePath) if (mLastSavegame == savePath)
{ {
if (character != nullptr) if (character->begin() != character->end())
mLastSavegame = character->begin()->mPath; mLastSavegame = character->begin()->mPath;
else else
mLastSavegame.clear(); mLastSavegame.clear();
@ -757,14 +757,12 @@ void MWState::StateManager::update(float duration)
if (mNewGameRequest) if (mNewGameRequest)
{ {
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
newGame(); newGame();
mNewGameRequest = false; mNewGameRequest = false;
} }
if (mLoadRequest) if (mLoadRequest)
{ {
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
loadGame(*mLoadRequest); loadGame(*mLoadRequest);
mLoadRequest = std::nullopt; mLoadRequest = std::nullopt;
} }

View file

@ -337,13 +337,13 @@ namespace
// helper function for forEachInternal // helper function for forEachInternal
template <class Visitor, class List> template <class Visitor, class List>
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; continue;
if (!visitor(MWWorld::Ptr(&v, &cellStore))) if (!visitor(MWWorld::Ptr(&*iter, cellStore)))
return false; return false;
} }
return true; 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 // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved
// objects are accounted for. // objects are accounted for.
template <class Visitor> template <class Visitor>
static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore, bool includeDeleted) static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore)
{ {
bool returnValue = true; bool returnValue = true;
Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&](auto& store) { Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) {
returnValue = returnValue && forEachImp(visitor, store, cellStore, includeDeleted); returnValue = returnValue && forEachImp(visitor, store, &cellStore);
}); });
return returnValue; return returnValue;
@ -583,11 +583,11 @@ namespace MWWorld
mMergedRefsNeedsUpdate = true; mMergedRefsNeedsUpdate = true;
} }
void CellStore::updateMergedRefs(bool includeDeleted) const void CellStore::updateMergedRefs() const
{ {
mMergedRefs.clear(); mMergedRefs.clear();
MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell);
CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this), includeDeleted); CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this));
visitor.merge(); visitor.merge();
mMergedRefsNeedsUpdate = false; mMergedRefsNeedsUpdate = false;
} }

View file

@ -213,13 +213,13 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEach(Visitor&& visitor, bool includeDeleted = false) bool forEach(Visitor&& visitor)
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(includeDeleted); updateMergedRefs();
if (mMergedRefs.empty()) if (mMergedRefs.empty())
return true; return true;
@ -227,7 +227,7 @@ namespace MWWorld
for (LiveCellRefBase* mergedRef : mMergedRefs) for (LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef)) if (!isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(mergedRef, this))) if (!visitor(MWWorld::Ptr(mergedRef, this)))
@ -242,17 +242,17 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEachConst(Visitor&& visitor, bool includeDeleted = false) const bool forEachConst(Visitor&& visitor) const
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(includeDeleted); updateMergedRefs();
for (const LiveCellRefBase* mergedRef : mMergedRefs) for (const LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef)) if (!isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::ConstPtr(mergedRef, this))) if (!visitor(MWWorld::ConstPtr(mergedRef, this)))
@ -267,25 +267,28 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class T, class Visitor> template <class T, class Visitor>
bool forEachType(Visitor&& visitor, bool includeDeleted = false) bool forEachType(Visitor&& visitor)
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(includeDeleted); updateMergedRefs();
if (mMergedRefs.empty()) if (mMergedRefs.empty())
return true; return true;
mHasState = true; mHasState = true;
for (LiveCellRefBase& base : get<T>().mList) CellRefList<T>& list = get<T>();
for (typename CellRefList<T>::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; continue;
if (!includeDeleted && !isAccessible(base.mData, base.mRef)) if (!isAccessible(base->mData, base->mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(&base, this))) if (!visitor(MWWorld::Ptr(base, this)))
return false; return false;
} }
@ -403,7 +406,7 @@ namespace MWWorld
/// Repopulate mMergedRefs. /// Repopulate mMergedRefs.
void requestMergedRefsUpdate(); void requestMergedRefsUpdate();
void updateMergedRefs(bool includeDeleted = false) const; void updateMergedRefs() const;
// (item, max charge) // (item, max charge)
typedef std::vector<std::pair<LiveCellRefBase*, float>> TRechargingItems; typedef std::vector<std::pair<LiveCellRefBase*, float>> TRechargingItems;

View file

@ -406,7 +406,8 @@ namespace MWWorld
template <class T> template <class T>
ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other) ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other)
{ {
static_assert(IsConvertible<T, PtrType, void>::value); char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible<T, PtrType, void>::value ? 1 : -1];
((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR);
copy(other); copy(other);
} }

View file

@ -70,7 +70,7 @@ namespace MWWorld
mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr); mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr);
reader.setModIndex(index); reader.setModIndex(index);
reader.updateModIndices(mNameToIndex); reader.updateModIndices(mNameToIndex);
mStore.loadESM4(reader, listener); mStore.loadESM4(reader);
break; break;
} }
} }

View file

@ -468,16 +468,9 @@ namespace MWWorld
} }
} }
void ESMStore::loadESM4(ESM4::Reader& reader, Loading::Listener* listener) void ESMStore::loadESM4(ESM4::Reader& reader)
{ {
if (listener != nullptr) auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); };
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;
};
ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {});
} }

View file

@ -219,7 +219,7 @@ namespace MWWorld
void validateDynamic(); void validateDynamic();
void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); 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 <class T> template <class T>
const Store<T>& get() const const Store<T>& get() const

View file

@ -367,7 +367,7 @@ namespace MWWorld
ListAndResetObjectsVisitor visitor; ListAndResetObjectsVisitor visitor;
cell->forEach(visitor, true); // Include objects being teleported by Lua cell->forEach(visitor);
for (const auto& ptr : visitor.mObjects) for (const auto& ptr : visitor.mObjects)
{ {
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))

View file

@ -290,14 +290,14 @@ namespace MWWorld
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat(); mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
} }
void World::init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, void World::init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue) SceneUtil::UnrefQueue& unrefQueue)
{ {
mPhysics = std::make_unique<MWPhysics::PhysicsSystem>(mResourceSystem, rootNode); mPhysics = std::make_unique<MWPhysics::PhysicsSystem>(mResourceSystem, rootNode);
if (Settings::navigator().mEnable) if (Settings::navigator().mEnable)
{ {
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(maxRecastLogLevel); auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath); mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath);
} }

View file

@ -4,7 +4,6 @@
#include <osg/Timer> #include <osg/Timer>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/debug/debuglog.hpp>
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -202,8 +201,8 @@ namespace MWWorld
Loading::Listener* listener); Loading::Listener* listener);
// Must be called after `loadData`. // Must be called after `loadData`.
void init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, void init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue); SceneUtil::UnrefQueue& unrefQueue);
virtual ~World(); virtual ~World();

View file

@ -2,7 +2,6 @@
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/settings/parser.hpp> #include <components/settings/parser.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -25,9 +24,5 @@ int main(int argc, char* argv[])
Settings::StaticValues::init(); Settings::StaticValues::init();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -59,7 +59,7 @@ list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}
add_component_dir (lua add_component_dir (lua
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
shapes/box inputactions yamlloader scripttracker luastateptr shapes/box inputactions yamlloader scripttracker
) )
add_component_dir (l10n add_component_dir (l10n

View file

@ -1,7 +1,6 @@
#include "gamesettings.hpp" #include "gamesettings.hpp"
#include <QDir> #include <QDir>
#include <QProgressDialog>
#include <QRegularExpression> #include <QRegularExpression>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -38,13 +37,8 @@ void Config::GameSettings::validatePaths()
mDataDirs.clear(); mDataDirs.clear();
QProgressDialog progressBar("Validating paths", {}, 0, paths.count() + 1);
progressBar.setWindowModality(Qt::WindowModal);
progressBar.setValue(0);
for (const auto& dataDir : paths) for (const auto& dataDir : paths)
{ {
progressBar.setValue(progressBar.value() + 1);
if (QDir(dataDir.value).exists()) if (QDir(dataDir.value).exists())
{ {
SettingValue copy = dataDir; SettingValue copy = dataDir;
@ -56,8 +50,6 @@ void Config::GameSettings::validatePaths()
// Do the same for data-local // Do the same for data-local
const QString& local = mSettings.value(QString("data-local")).value; const QString& local = mSettings.value(QString("data-local")).value;
progressBar.setValue(progressBar.value() + 1);
if (!local.isEmpty() && QDir(local).exists()) if (!local.isEmpty() && QDir(local).exists())
{ {
mDataLocal = QDir(local).canonicalPath(); 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 // - Always ignore a line beginning with '#' or empty lines; added above a config
// entry. // entry.
// //
// - If a line in file exists with matching key and value, then replace the line with that of mUserSettings. // - If a line in file exists with matching key and first part of value (before ',',
// - else if only the key matches, remove comment // '\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 // - 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()) if (fileCopy.empty())
return writeFile(stream); return writeFile(stream);
QMultiMap<QString, SettingValue> 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 // start
// | // |
// | +----------------------------------------------------------+ // | +----------------------------------------------------------+
@ -351,7 +325,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
// +----------------------------------------------------------+ // +----------------------------------------------------------+
// //
// //
QRegularExpression settingRegex("^([^=]+)\\s*=\\s*(.+?)\\s*$"); QRegularExpression settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$");
std::vector<QString> comments; std::vector<QString> comments;
auto commentStart = fileCopy.end(); auto commentStart = fileCopy.end();
std::map<QString, std::vector<QString>> commentsMap; std::map<QString, std::vector<QString>> commentsMap;
@ -421,7 +395,8 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
// look for a key in the line // look for a key in the line
if (!match.hasMatch() || settingRegex.captureCount() < 2) 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(); *iter = QString();
comments.clear(); comments.clear();
commentStart = fileCopy.end(); commentStart = fileCopy.end();

View file

@ -10,7 +10,6 @@
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QDirIterator>
#include <QFont> #include <QFont>
#include <QIODevice> #include <QIODevice>
@ -79,10 +78,14 @@ ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(int row)
} }
const ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(const QString& name) const 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) 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 file;
} }
return nullptr; return nullptr;
@ -307,6 +310,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
{ {
setCheckState(file->filePath(), success); setCheckState(file->filePath(), success);
emit dataChanged(index, index); emit dataChanged(index, index);
checkForLoadOrderErrors();
} }
else else
return success; return success;
@ -421,6 +425,7 @@ bool ContentSelectorModel::ContentModel::dropMimeData(
dataChanged(index(minRow, 0), index(maxRow, 0)); dataChanged(index(minRow, 0), index(maxRow, 0));
// at this point we know that drag and drop has finished. // at this point we know that drag and drop has finished.
checkForLoadOrderErrors();
return true; return true;
} }
@ -547,13 +552,15 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path) bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path)
{ {
QDir dir(path);
QStringList filters; QStringList filters;
filters << "*.esp" filters << "*.esp"
<< "*.esm" << "*.esm"
<< "*.omwgame" << "*.omwgame"
<< "*.omwaddon"; << "*.omwaddon";
QDirIterator it(path, filters, QDir::Files | QDir::NoDotAndDotDot); dir.setNameFilters(filters);
return it.hasNext();
return dir.entryList().count() != 0;
} }
void ContentSelectorModel::ContentModel::clearFiles() void ContentSelectorModel::ContentModel::clearFiles()
@ -700,13 +707,12 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
{ {
int index = indexFromItem(file).row(); return mPluginsWithLoadOrderError.contains(file->filePath());
auto errors = checkForLoadOrderErrors(file, index);
return !errors.empty();
} }
void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList) void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList)
{ {
mPluginsWithLoadOrderError.clear();
int previousPosition = -1; int previousPosition = -1;
for (const QString& filepath : fileList) for (const QString& filepath : fileList)
{ {
@ -719,6 +725,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
if (filePosition < previousPosition) if (filePosition < previousPosition)
{ {
mFiles.move(filePosition, previousPosition); mFiles.move(filePosition, previousPosition);
emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex()));
} }
else 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::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors( QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors(
@ -767,12 +791,11 @@ QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::
QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
{ {
int index = indexFromItem(file).row(); if (isLoadOrderError(file))
auto errors = checkForLoadOrderErrors(file, index);
if (!errors.empty())
{ {
QString text("<b>"); QString text("<b>");
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); assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
@ -877,6 +900,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
void ContentSelectorModel::ContentModel::uncheckAll() void ContentSelectorModel::ContentModel::uncheckAll()
{ {
emit layoutAboutToBeChanged();
mCheckedFiles.clear(); mCheckedFiles.clear();
emit dataChanged(index(0, 0), index(rowCount(), columnCount()), { Qt::CheckStateRole, Qt::UserRole + 1 }); emit layoutChanged();
} }

View file

@ -69,6 +69,9 @@ namespace ContentSelectorModel
void refreshModel(); void refreshModel();
/// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues
void checkForLoadOrderErrors();
private: private:
void addFile(EsmFile* file); void addFile(EsmFile* file);
@ -86,6 +89,7 @@ namespace ContentSelectorModel
QStringList mNonUserContent; QStringList mNonUserContent;
std::set<const EsmFile*> mCheckedFiles; std::set<const EsmFile*> mCheckedFiles;
QHash<QString, bool> mNewFiles; QHash<QString, bool> mNewFiles;
QSet<QString> mPluginsWithLoadOrderError;
QString mEncoding; QString mEncoding;
QIcon mWarningIcon; QIcon mWarningIcon;
QIcon mErrorIcon; QIcon mErrorIcon;

View file

@ -48,18 +48,18 @@ namespace ContentSelectorModel
void addGameFile(const QString& name) { mGameFiles.append(name); } void addGameFile(const QString& name) { mGameFiles.append(name); }
QVariant fileProperty(const FileProperty prop) const; QVariant fileProperty(const FileProperty prop) const;
const QString& fileName() const { return mFileName; } QString fileName() const { return mFileName; }
const QString& author() const { return mAuthor; } QString author() const { return mAuthor; }
QDateTime modified() const { return mModified; } QDateTime modified() const { return mModified; }
const QString& formatVersion() const { return mVersion; } QString formatVersion() const { return mVersion; }
const QString& filePath() const { return mPath; } QString filePath() const { return mPath; }
bool builtIn() const { return mBuiltIn; } bool builtIn() const { return mBuiltIn; }
bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; }
bool isMissing() const { return mPath.isEmpty(); } bool isMissing() const { return mPath.isEmpty(); }
/// @note Contains file names, not paths. /// @note Contains file names, not paths.
const QStringList& gameFiles() const { return mGameFiles; } const QStringList& gameFiles() const { return mGameFiles; }
const QString& description() const { return mDescription; } QString description() const { return mDescription; }
QString toolTip() const QString toolTip() const
{ {
if (isMissing()) if (isMissing())

View file

@ -211,6 +211,7 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne
ui->gameFileView->setCurrentIndex(0); ui->gameFileView->setCurrentIndex(0);
mContentModel->uncheckAll(); mContentModel->uncheckAll();
mContentModel->checkForLoadOrderErrors();
} }
void ContentSelectorView::ContentSelector::sortFiles() void ContentSelectorView::ContentSelector::sortFiles()
@ -253,6 +254,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i
oldIndex = index; oldIndex = index;
setGameFileSelected(index, true); setGameFileSelected(index, true);
mContentModel->checkForLoadOrderErrors();
} }
emit signalCurrentGamefileIndexChanged(index); emit signalCurrentGamefileIndexChanged(index);

View file

@ -237,7 +237,7 @@ namespace Crash
// must remain until monitor has finished // must remain until monitor has finished
waitMonitor(); 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()) + Misc::StringUtils::u8StringToString(getCrashDumpPath(*mShm).u8string())
+ "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);

Some files were not shown because too many files have changed in this diff Show more