mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 21:07:59 +03:00
Compare commits
118 commits
openmw-49-
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
55107e0913 | ||
![]() |
011dfb305c | ||
![]() |
fc4cc3255d | ||
![]() |
f487a6332b | ||
![]() |
80d6f020ed | ||
![]() |
c7c95c5a85 | ||
![]() |
1948ab21f7 | ||
![]() |
ca3a286cc4 | ||
![]() |
ab5070328d | ||
![]() |
23522ed314 | ||
![]() |
b4d5013679 | ||
![]() |
5ef2cf23b3 | ||
![]() |
8ee0c9e7be | ||
![]() |
19725473d7 | ||
![]() |
aed135a7c0 | ||
![]() |
928bbed09b | ||
![]() |
626d7b2282 | ||
![]() |
e20d52d23c | ||
![]() |
05c624bc34 | ||
![]() |
5a42db3256 | ||
![]() |
6f89d38b78 | ||
![]() |
042c4b2b9d | ||
![]() |
f80283422f | ||
![]() |
972995d124 | ||
![]() |
0d5e9ef85f | ||
![]() |
87d77a6882 | ||
![]() |
c1c8769742 | ||
![]() |
271ab2e109 | ||
![]() |
583620e607 | ||
![]() |
6dd2cac3ec | ||
![]() |
6aed2d8284 | ||
![]() |
84f471ce5c | ||
![]() |
deb070389f | ||
![]() |
f7f148a6ca | ||
![]() |
a5a6f33578 | ||
![]() |
d74a0edb82 | ||
![]() |
396cd1c727 | ||
![]() |
31fcc5e126 | ||
![]() |
22172f3b0e | ||
![]() |
b68935e917 | ||
![]() |
e512a8e74f | ||
![]() |
48572e4c96 | ||
![]() |
1d1ae1c906 | ||
![]() |
586467540b | ||
![]() |
37dc1a6a76 | ||
![]() |
15162a734d | ||
![]() |
ea8369eff0 | ||
![]() |
096759435a | ||
![]() |
894ea4ba62 | ||
![]() |
d6b61f1f54 | ||
![]() |
e779f115ef | ||
![]() |
428044abe2 | ||
![]() |
bd1c2a11d7 | ||
![]() |
0c4c202998 | ||
![]() |
8419116cae | ||
![]() |
8a0f513094 | ||
![]() |
1667b11564 | ||
![]() |
b29d89bd6a | ||
![]() |
211a5e5bda | ||
![]() |
2ed14de41f | ||
![]() |
d826962eaa | ||
![]() |
962ef91e25 | ||
![]() |
ed62f9b12b | ||
![]() |
973282e471 | ||
![]() |
7bad2864d9 | ||
![]() |
1237746549 | ||
![]() |
7254bb74a4 | ||
![]() |
3af2091b28 | ||
![]() |
621a0a15a3 | ||
![]() |
c34b0f90d7 | ||
![]() |
e098770ba2 | ||
![]() |
7c45a564a1 | ||
![]() |
da388c93eb | ||
![]() |
d609bd1ab1 | ||
![]() |
15f4368fe6 | ||
![]() |
3901084cc2 | ||
![]() |
065a388632 | ||
![]() |
9a6807f862 | ||
![]() |
3523ba564a | ||
![]() |
1629ea32f7 | ||
![]() |
86426aa87b | ||
![]() |
a61ce111a5 | ||
![]() |
11c2fd9e3d | ||
![]() |
b6be7cdd56 | ||
![]() |
caef91d261 | ||
![]() |
72aefbf191 | ||
![]() |
747771ac5e | ||
![]() |
5f413e7b4d | ||
![]() |
2ebdc43bbe | ||
![]() |
536325e0ba | ||
![]() |
cbcd4f6acd | ||
![]() |
9570b29a0a | ||
![]() |
166852254f | ||
![]() |
f8be5fdd2a | ||
![]() |
ada48d9021 | ||
![]() |
7112217adc | ||
![]() |
87a2f776b7 | ||
![]() |
d13f108779 | ||
![]() |
57fb334a6e | ||
![]() |
88c673de51 | ||
![]() |
51258662b5 | ||
![]() |
e5e21eef20 | ||
![]() |
057c85b710 | ||
![]() |
241a24564a | ||
![]() |
cd3980eca4 | ||
![]() |
c2744a1846 | ||
![]() |
8d0dcb774f | ||
![]() |
b5a2a4e52d | ||
![]() |
e4ae0c9a95 | ||
![]() |
e5f6b77c29 | ||
![]() |
2892e19c43 | ||
![]() |
0e19b1dd75 | ||
![]() |
7a9c2d5e88 | ||
![]() |
981ca957c1 | ||
![]() |
8b62f02523 | ||
![]() |
c298210844 | ||
![]() |
f80c7b2355 | ||
![]() |
dc3264a3a5 |
106 changed files with 1528 additions and 837 deletions
21
.clang-tidy
21
.clang-tidy
|
@ -1,18 +1,15 @@
|
||||||
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,
|
||||||
WarningsAsErrors: >
|
readability-identifier-naming
|
||||||
-*,
|
WarningsAsErrors: '*'
|
||||||
boost-*,
|
HeaderFilterRegex: '(apps|components)/'
|
||||||
portability-*,
|
CheckOptions:
|
||||||
clang-analyzer-*,
|
- key: readability-identifier-naming.ConceptCase
|
||||||
-clang-analyzer-optin*,
|
value: CamelCase
|
||||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
|
||||||
-clang-analyzer-core.CallAndMessage
|
|
||||||
HeaderFilterRegex: '^(apps|components)'
|
|
||||||
|
|
|
@ -632,12 +632,16 @@ macOS14_Xcode15_arm64:
|
||||||
- |
|
- |
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -645,13 +649,20 @@ 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-v11
|
key: ninja-2022-v12
|
||||||
paths:
|
paths:
|
||||||
- ccache
|
- ccache
|
||||||
- deps
|
- deps
|
||||||
|
@ -779,12 +790,16 @@ macOS14_Xcode15_arm64:
|
||||||
- |
|
- |
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -792,13 +807,20 @@ 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-v11
|
key: msbuild-2022-v12
|
||||||
paths:
|
paths:
|
||||||
- deps
|
- deps
|
||||||
- MSVC2022_64/deps/Qt
|
- MSVC2022_64/deps/Qt
|
||||||
|
|
|
@ -229,6 +229,9 @@
|
||||||
Bug #8299: Crash while smoothing landscape
|
Bug #8299: Crash while smoothing landscape
|
||||||
Bug #8364: Crash when clicking scrollbar without handle (divide by zero)
|
Bug #8364: Crash when clicking scrollbar without handle (divide by zero)
|
||||||
Bug #8378: Korean bitmap fonts are unusable
|
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
|
||||||
|
|
|
@ -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;--warnings-as-errors=*"
|
-DCMAKE_CXX_CLANG_TIDY=clang-tidy
|
||||||
-DBUILD_COMPONENTS_TESTS=ON
|
-DBUILD_COMPONENTS_TESTS=ON
|
||||||
-DBUILD_OPENMW_TESTS=ON
|
-DBUILD_OPENMW_TESTS=ON
|
||||||
-DBUILD_OPENCS_TESTS=ON
|
-DBUILD_OPENCS_TESTS=ON
|
||||||
|
|
|
@ -95,7 +95,7 @@ declare -rA GROUPED_DEPS=(
|
||||||
[libasan6]="libasan6"
|
[libasan6]="libasan6"
|
||||||
|
|
||||||
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
|
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
|
||||||
|
|
||||||
[openmw-clang-format]="
|
[openmw-clang-format]="
|
||||||
clang-format-14
|
clang-format-14
|
||||||
git-core
|
git-core
|
||||||
|
@ -126,10 +126,24 @@ 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
|
|
||||||
add-apt-repository -y ppa:openmw/openmw-daily
|
while true; do
|
||||||
add-apt-repository -y ppa:openmw/staging
|
add-apt-repository -y ppa:openmw/openmw && break
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
add-apt-repository -y ppa:openmw/openmw-daily && break
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
add-apt-repository -y ppa:openmw/staging && break
|
||||||
|
done
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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 --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
|
scripts/integration_tests.py --verbose --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}"
|
||||||
|
|
|
@ -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 71)
|
set(OPENMW_LUA_API_REVISION 72)
|
||||||
set(OPENMW_POSTPROCESSING_API_REVISION 2)
|
set(OPENMW_POSTPROCESSING_API_REVISION 2)
|
||||||
|
|
||||||
set(OPENMW_VERSION_COMMITHASH "")
|
set(OPENMW_VERSION_COMMITHASH "")
|
||||||
|
@ -860,7 +860,6 @@ 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 "")
|
||||||
|
|
|
@ -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 (auto _ : state)
|
for ([[maybe_unused]] 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);
|
||||||
|
|
|
@ -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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] auto _ : state)
|
||||||
{
|
{
|
||||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||||
if (++i >= serializedRefIds.size())
|
if (++i >= serializedRefIds.size())
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace
|
||||||
{
|
{
|
||||||
void settingsManager(benchmark::State& state)
|
void settingsManager(benchmark::State& state)
|
||||||
{
|
{
|
||||||
for (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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 (auto _ : state)
|
for ([[maybe_unused]] 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"));
|
||||||
|
|
|
@ -53,8 +53,6 @@ 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");
|
||||||
|
@ -87,7 +85,8 @@ 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", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(),
|
addOption("fallback",
|
||||||
|
bpo::value<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")->multitoken()->composing(),
|
||||||
"fallback values");
|
"fallback values");
|
||||||
|
|
||||||
Files::ConfigurationManager::addCommonOptions(result);
|
Files::ConfigurationManager::addCommonOptions(result);
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#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>
|
||||||
|
|
||||||
|
@ -372,6 +374,106 @@ 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) };
|
||||||
|
|
|
@ -88,4 +88,31 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace
|
||||||
|
|
||||||
TEST(FilesGetHash, shouldClearErrors)
|
TEST(FilesGetHash, shouldClearErrors)
|
||||||
{
|
{
|
||||||
const auto fileName = temporaryFilePath("fileName");
|
const auto fileName = outputFilePath("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 = temporaryFilePath("fileName");
|
const auto fileName = outputFilePath("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);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#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>
|
||||||
|
|
||||||
|
@ -24,5 +25,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace
|
||||||
ShaderManager mManager;
|
ShaderManager mManager;
|
||||||
ShaderManager::DefineMap mDefines;
|
ShaderManager::DefineMap mDefines;
|
||||||
|
|
||||||
ShaderManagerTest() { mManager.setShaderPath("tests_output"); }
|
ShaderManagerTest() { mManager.setShaderPath(TestingOpenMW::outputDir()); }
|
||||||
|
|
||||||
template <class F>
|
template <class F>
|
||||||
void withShaderFile(const std::string& content, F&& f)
|
void withShaderFile(const std::string& content, F&& f)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
|
#include <QProgressDialog>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -351,9 +352,17 @@ 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;
|
||||||
|
|
||||||
|
@ -402,7 +411,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(QIcon(":/images/openmw-plugin.png"));
|
item->setIcon(containsDataIcon);
|
||||||
|
|
||||||
tooltip << tr("Contains content file(s)");
|
tooltip << tr("Contains content file(s)");
|
||||||
}
|
}
|
||||||
|
@ -765,7 +774,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString rootPath = QFileDialog::getExistingDirectory(
|
QString rootPath = QFileDialog::getExistingDirectory(
|
||||||
this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly);
|
this, tr("Select Directory"), {}, QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly);
|
||||||
|
|
||||||
if (rootPath.isEmpty())
|
if (rootPath.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
set(NAVMESHTOOL
|
set(NAVMESHTOOL_LIB
|
||||||
worldspacedata.cpp
|
worldspacedata.cpp
|
||||||
navmesh.cpp
|
navmesh.cpp
|
||||||
main.cpp
|
|
||||||
)
|
)
|
||||||
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL})
|
|
||||||
|
|
||||||
add_library(openmw-navmeshtool-lib STATIC
|
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL_LIB} main.cpp)
|
||||||
${NAVMESHTOOL}
|
|
||||||
)
|
add_library(openmw-navmeshtool-lib STATIC ${NAVMESHTOOL_LIB})
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
add_library(openmw-navmeshtool SHARED
|
add_library(openmw-navmeshtool SHARED main.cpp)
|
||||||
main.cpp
|
|
||||||
)
|
|
||||||
else()
|
else()
|
||||||
openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL})
|
openmw_add_executable(openmw-navmeshtool main.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib)
|
target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib)
|
||||||
|
|
|
@ -62,8 +62,6 @@ 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");
|
||||||
|
@ -225,7 +223,8 @@ 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::makeSettingsFromSettingsManager();
|
DetourNavigator::Settings navigatorSettings
|
||||||
|
= DetourNavigator::makeSettingsFromSettingsManager(Debug::getRecastMaxLogLevel());
|
||||||
navigatorSettings.mRecast.mSwimHeightScale
|
navigatorSettings.mRecast.mSwimHeightScale
|
||||||
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
|
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,6 @@
|
||||||
|
|
||||||
#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)
|
||||||
|
@ -124,7 +122,10 @@ 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<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(),
|
boost::program_options::value<Fallback::FallbackMap>()
|
||||||
|
->default_value(Fallback::FallbackMap(), "")
|
||||||
|
->multitoken()
|
||||||
|
->composing(),
|
||||||
"fallback values");
|
"fallback values");
|
||||||
Files::ConfigurationManager::addCommonOptions(desc);
|
Files::ConfigurationManager::addCommonOptions(desc);
|
||||||
|
|
||||||
|
@ -141,7 +142,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<FallbackMap>().mMap);
|
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
||||||
|
|
||||||
mEncodingName = variables["encoding"].as<std::string>();
|
mEncodingName = variables["encoding"].as<std::string>();
|
||||||
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
|
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <components/debug/debugging.hpp>
|
#include <components/debug/debugging.hpp>
|
||||||
|
#include <components/testing/util.hpp>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
@ -7,5 +8,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,11 +373,15 @@ 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
|
||||||
|
@ -807,7 +811,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,
|
mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
|
||||||
Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr);
|
Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr);
|
||||||
mEnvironment.setWindowManager(*mWindowManager);
|
mEnvironment.setWindowManager(*mWindowManager);
|
||||||
|
|
||||||
|
@ -847,7 +851,7 @@ void OMW::Engine::prepareEngine()
|
||||||
}
|
}
|
||||||
listener->loadingOff();
|
listener->loadingOff();
|
||||||
|
|
||||||
mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue);
|
mWorld->init(mMaxRecastLogLevel, 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);
|
||||||
|
@ -1109,6 +1113,11 @@ 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;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#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>
|
||||||
|
@ -171,7 +172,9 @@ 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;
|
||||||
|
@ -180,6 +183,9 @@ 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&);
|
||||||
|
@ -251,14 +257,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);
|
||||||
|
|
||||||
private:
|
void setRecastMaxLogLevel(Debug::Level value) { mMaxRecastLogLevel = value; }
|
||||||
Files::ConfigurationManager& mCfgMgr;
|
|
||||||
int mGlMaxTextureImageUnits;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,6 @@ 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.
|
||||||
|
@ -152,9 +150,10 @@ 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<FallbackMap>().mMap);
|
Fallback::Map::init(variables["fallback"].as<Fallback::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;
|
||||||
|
@ -220,6 +219,8 @@ 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())
|
||||||
|
|
|
@ -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, const std::string& groupName) = 0;
|
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) = 0;
|
||||||
|
|
||||||
virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;
|
virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;
|
||||||
|
|
||||||
|
|
|
@ -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() = 0;
|
virtual bool isWindowVisible() const = 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;
|
||||||
|
|
|
@ -237,7 +237,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1343,12 +1343,13 @@ 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
|
||||||
|
@ -1373,12 +1374,14 @@ 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
|
||||||
|
@ -1388,15 +1391,12 @@ 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;
|
||||||
|
|
|
@ -29,11 +29,26 @@ 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 finished playing, start again
|
if (windowManager.isWindowVisible())
|
||||||
if (!mVideo->update())
|
{
|
||||||
mVideo->playVideo("video\\menu_background.bik");
|
if (paused)
|
||||||
|
{
|
||||||
|
mVideo->resume();
|
||||||
|
paused = false;
|
||||||
|
}
|
||||||
|
// If finished playing, start again
|
||||||
|
if (!mVideo->update())
|
||||||
|
mVideo->playVideo("video\\menu_background.bik");
|
||||||
|
}
|
||||||
|
else if (!paused)
|
||||||
|
{
|
||||||
|
paused = true;
|
||||||
|
mVideo->pause();
|
||||||
|
}
|
||||||
frameRateLimiter.limit();
|
frameRateLimiter.limit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr)
|
bool exportFonts, const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr)
|
||||||
: mOldUpdateMask(0)
|
: mOldUpdateMask(0)
|
||||||
, mOldCullMask(0)
|
, mOldCullMask(0)
|
||||||
, mStore(nullptr)
|
, mStore(nullptr)
|
||||||
|
@ -215,7 +215,8 @@ 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 = std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor);
|
mFontLoader
|
||||||
|
= 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");
|
||||||
|
@ -729,6 +730,9 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -1214,7 +1218,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()
|
bool WindowManager::isWindowVisible() const
|
||||||
{
|
{
|
||||||
return mWindowVisible;
|
return mWindowVisible;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, const std::string& versionDescription, bool useShaders,
|
ToUTF8::FromType encoding, bool exportFonts, 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() override;
|
bool isWindowVisible() const 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;
|
||||||
|
|
|
@ -213,6 +213,7 @@ 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 },
|
||||||
|
|
|
@ -289,8 +289,9 @@ 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 = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
|
const ESM::Enchantment* enchantment
|
||||||
if (enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
|
= world->getStore().get<ESM::Enchantment>().search(enchantmentId);
|
||||||
|
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) {
|
||||||
|
|
|
@ -2028,7 +2028,7 @@ namespace MWMechanics
|
||||||
iter->second->getCharacterController().skipAnim();
|
iter->second->getCharacterController().skipAnim();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const
|
bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const
|
||||||
{
|
{
|
||||||
const auto iter = mIndex.find(ptr.mRef);
|
const auto iter = mIndex.find(ptr.mRef);
|
||||||
if (iter != mIndex.end())
|
if (iter != mIndex.end())
|
||||||
|
|
|
@ -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, const std::string& groupName) const;
|
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view 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);
|
||||||
|
|
|
@ -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 AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||||
std::string("idle2"),
|
"idle2",
|
||||||
std::string("idle3"),
|
"idle3",
|
||||||
std::string("idle4"),
|
"idle4",
|
||||||
std::string("idle5"),
|
"idle5",
|
||||||
std::string("idle6"),
|
"idle6",
|
||||||
std::string("idle7"),
|
"idle7",
|
||||||
std::string("idle8"),
|
"idle8",
|
||||||
std::string("idle9"),
|
"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& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle];
|
const std::string_view 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& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle];
|
const std::string_view groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle];
|
||||||
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName);
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "typedaipackage.hpp"
|
#include "typedaipackage.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "aitemporarybase.hpp"
|
#include "aitemporarybase.hpp"
|
||||||
|
@ -181,9 +182,7 @@ 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 sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
|
static const std::string_view sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
|
||||||
|
|
||||||
static int OffsetToPreventOvercrowding();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -778,7 +778,8 @@ 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);
|
||||||
|
|
|
@ -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, const std::string& groupName) override;
|
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view 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;
|
||||||
|
|
|
@ -161,7 +161,7 @@ namespace MWRender
|
||||||
|
|
||||||
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||||
{
|
{
|
||||||
if (Settings::game().mShieldSheathing)
|
if (Settings::game().mShieldSheathing && mObjectRoot)
|
||||||
{
|
{
|
||||||
const MWWorld::Class& cls = mPtr.getClass();
|
const MWWorld::Class& cls = mPtr.getClass();
|
||||||
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);
|
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);
|
||||||
|
|
|
@ -1695,7 +1695,7 @@ namespace MWRender
|
||||||
mGlowUpdater->setColor(color);
|
mGlowUpdater->setColor(color);
|
||||||
mGlowUpdater->setDuration(glowDuration);
|
mGlowUpdater->setDuration(glowDuration);
|
||||||
}
|
}
|
||||||
else
|
else if (mObjectRoot)
|
||||||
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)
|
if (alpha == mAlpha || !mObjectRoot)
|
||||||
return;
|
return;
|
||||||
mAlpha = alpha;
|
mAlpha = alpha;
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,8 @@ namespace MWRender
|
||||||
void CreatureWeaponAnimation::addControllers()
|
void CreatureWeaponAnimation::addControllers()
|
||||||
{
|
{
|
||||||
Animation::addControllers();
|
Animation::addControllers();
|
||||||
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
|
if (mObjectRoot)
|
||||||
|
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration)
|
osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration)
|
||||||
|
|
|
@ -49,18 +49,6 @@ namespace MWRender
|
||||||
&& exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute;
|
&& exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool pipelineLogged = false;
|
|
||||||
|
|
||||||
if (!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";
|
|
||||||
|
|
||||||
pipelineLogged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
@ -98,6 +86,18 @@ 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,21 +109,36 @@ 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(
|
mProgramBlobber = shaderManager.getProgram(vertex, std::move(blobber));
|
||||||
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT));
|
mProgramSimulation = shaderManager.getProgram(std::move(vertex), std::move(simulate));
|
||||||
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();
|
||||||
|
|
||||||
mProgramBlobber = shaderManager.getProgram(
|
osg::ref_ptr<osg::Shader> blobber
|
||||||
nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE));
|
= shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE);
|
||||||
mProgramSimulation = shaderManager.getProgram(
|
osg::ref_ptr<osg::Shader> simulate
|
||||||
nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE));
|
= 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)
|
||||||
|
@ -191,6 +206,9 @@ 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];
|
||||||
|
|
|
@ -406,8 +406,7 @@ namespace MWWorld
|
||||||
template <class T>
|
template <class T>
|
||||||
ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other)
|
ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other)
|
||||||
{
|
{
|
||||||
char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible<T, PtrType, void>::value ? 1 : -1];
|
static_assert(IsConvertible<T, PtrType, void>::value);
|
||||||
((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR);
|
|
||||||
copy(other);
|
copy(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
|
void World::init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||||
SceneUtil::UnrefQueue& unrefQueue)
|
SceneUtil::WorkQueue* workQueue, 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();
|
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(maxRecastLogLevel);
|
||||||
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
|
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
|
||||||
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath);
|
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#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>
|
||||||
|
@ -201,8 +202,8 @@ namespace MWWorld
|
||||||
Loading::Listener* listener);
|
Loading::Listener* listener);
|
||||||
|
|
||||||
// Must be called after `loadData`.
|
// Must be called after `loadData`.
|
||||||
void init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
|
void init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||||
SceneUtil::UnrefQueue& unrefQueue);
|
SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue);
|
||||||
|
|
||||||
virtual ~World();
|
virtual ~World();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#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>
|
||||||
|
|
||||||
|
@ -24,5 +25,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
shapes/box inputactions yamlloader scripttracker luastateptr
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (l10n
|
add_component_dir (l10n
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#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>
|
||||||
|
@ -37,8 +38,13 @@ 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;
|
||||||
|
@ -50,6 +56,8 @@ 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();
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
|
||||||
|
@ -78,14 +79,10 @@ 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
|
||||||
{
|
{
|
||||||
EsmFile::FileProperty fp = EsmFile::FileProperty_FileName;
|
bool path = name.contains('/');
|
||||||
|
|
||||||
if (name.contains('/'))
|
|
||||||
fp = EsmFile::FileProperty_FilePath;
|
|
||||||
|
|
||||||
for (const EsmFile* file : mFiles)
|
for (const EsmFile* file : mFiles)
|
||||||
{
|
{
|
||||||
if (name.compare(file->fileProperty(fp).toString(), Qt::CaseInsensitive) == 0)
|
if (name.compare(path ? file->filePath() : file->fileName(), Qt::CaseInsensitive) == 0)
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -310,7 +307,6 @@ 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;
|
||||||
|
@ -425,7 +421,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -552,15 +547,13 @@ 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";
|
||||||
dir.setNameFilters(filters);
|
QDirIterator it(path, filters, QDir::Files | QDir::NoDotAndDotDot);
|
||||||
|
return it.hasNext();
|
||||||
return dir.entryList().count() != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentSelectorModel::ContentModel::clearFiles()
|
void ContentSelectorModel::ContentModel::clearFiles()
|
||||||
|
@ -707,12 +700,13 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi
|
||||||
|
|
||||||
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
|
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
|
||||||
{
|
{
|
||||||
return mPluginsWithLoadOrderError.contains(file->filePath());
|
int index = indexFromItem(file).row();
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
@ -725,7 +719,6 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -733,24 +726,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkForLoadOrderErrors();
|
emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
||||||
|
@ -791,11 +767,12 @@ QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::
|
||||||
|
|
||||||
QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
|
QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
|
||||||
{
|
{
|
||||||
if (isLoadOrderError(file))
|
int index = indexFromItem(file).row();
|
||||||
|
auto errors = checkForLoadOrderErrors(file, index);
|
||||||
|
if (!errors.empty())
|
||||||
{
|
{
|
||||||
QString text("<b>");
|
QString text("<b>");
|
||||||
int index = indexFromItem(item(file->filePath())).row();
|
for (const LoadOrderError& error : errors)
|
||||||
for (const LoadOrderError& error : checkForLoadOrderErrors(file, index))
|
|
||||||
{
|
{
|
||||||
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
|
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
|
||||||
|
|
||||||
|
@ -900,7 +877,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
|
||||||
|
|
||||||
void ContentSelectorModel::ContentModel::uncheckAll()
|
void ContentSelectorModel::ContentModel::uncheckAll()
|
||||||
{
|
{
|
||||||
emit layoutAboutToBeChanged();
|
|
||||||
mCheckedFiles.clear();
|
mCheckedFiles.clear();
|
||||||
emit layoutChanged();
|
emit dataChanged(index(0, 0), index(rowCount(), columnCount()), { Qt::CheckStateRole, Qt::UserRole + 1 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,9 +69,6 @@ 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);
|
||||||
|
|
||||||
|
@ -89,7 +86,6 @@ 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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
QString fileName() const { return mFileName; }
|
const QString& fileName() const { return mFileName; }
|
||||||
QString author() const { return mAuthor; }
|
const QString& author() const { return mAuthor; }
|
||||||
QDateTime modified() const { return mModified; }
|
QDateTime modified() const { return mModified; }
|
||||||
QString formatVersion() const { return mVersion; }
|
const QString& formatVersion() const { return mVersion; }
|
||||||
QString filePath() const { return mPath; }
|
const 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; }
|
||||||
QString description() const { return mDescription; }
|
const QString& description() const { return mDescription; }
|
||||||
QString toolTip() const
|
QString toolTip() const
|
||||||
{
|
{
|
||||||
if (isMissing())
|
if (isMissing())
|
||||||
|
|
|
@ -211,7 +211,6 @@ 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()
|
||||||
|
@ -254,7 +253,6 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i
|
||||||
oldIndex = index;
|
oldIndex = index;
|
||||||
|
|
||||||
setGameFileSelected(index, true);
|
setGameFileSelected(index, true);
|
||||||
mContentModel->checkForLoadOrderErrors();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit signalCurrentGamefileIndexChanged(index);
|
emit signalCurrentGamefileIndexChanged(index);
|
||||||
|
|
|
@ -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 log saved to '"
|
std::string message = "OpenMW has encountered a fatal error.\nCrash dump 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);
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Crash
|
||||||
// the main openmw process in task manager.
|
// the main openmw process in task manager.
|
||||||
|
|
||||||
static constexpr const int CrashCatcherTimeout = 2500;
|
static constexpr const int CrashCatcherTimeout = 2500;
|
||||||
|
static constexpr const int CrashCatcherThawTimeout = 250;
|
||||||
|
|
||||||
struct CrashSHM;
|
struct CrashSHM;
|
||||||
|
|
||||||
|
|
|
@ -87,9 +87,10 @@ namespace Crash
|
||||||
SetEvent(mSignalAppEvent);
|
SetEvent(mSignalAppEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CrashMonitor::waitApp() const
|
bool CrashMonitor::waitApp(bool thawMode) const
|
||||||
{
|
{
|
||||||
return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0;
|
return WaitForSingleObject(mSignalMonitorEvent, thawMode ? CrashCatcherThawTimeout : CrashCatcherTimeout)
|
||||||
|
== WAIT_OBJECT_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CrashMonitor::isAppAlive() const
|
bool CrashMonitor::isAppAlive() const
|
||||||
|
@ -185,7 +186,7 @@ namespace Crash
|
||||||
frozen = false;
|
frozen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mFreezeAbort && waitApp())
|
if (!mFreezeAbort && waitApp(frozen))
|
||||||
{
|
{
|
||||||
shmLock();
|
shmLock();
|
||||||
|
|
||||||
|
@ -215,7 +216,7 @@ namespace Crash
|
||||||
{
|
{
|
||||||
handleCrash(true);
|
handleCrash(true);
|
||||||
TerminateProcess(mAppProcessHandle, 0xDEAD);
|
TerminateProcess(mAppProcessHandle, 0xDEAD);
|
||||||
std::string message = "OpenMW appears to have frozen.\nCrash log saved to '"
|
std::string message = "OpenMW has frozen.\nCrash dump saved to '"
|
||||||
+ Misc::StringUtils::u8StringToString(getFreezeDumpPath(*mShm).u8string())
|
+ Misc::StringUtils::u8StringToString(getFreezeDumpPath(*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);
|
||||||
|
@ -289,10 +290,10 @@ namespace Crash
|
||||||
{
|
{
|
||||||
std::thread messageBoxThread([&]() {
|
std::thread messageBoxThread([&]() {
|
||||||
SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" };
|
SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" };
|
||||||
SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW appears to have frozen",
|
SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW has frozen",
|
||||||
"OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW "
|
"OpenMW has frozen. This should never happen. Press Abort to terminate it and generate a crash dump to "
|
||||||
"hasn't actually frozen, this message box will disappear a within a few seconds of it becoming "
|
"help diagnose the problem.\nOpenMW may unfreeze if you wait, and this message box will disappear "
|
||||||
"responsive.",
|
"after it becomes responsive.",
|
||||||
1, &button, nullptr };
|
1, &button, nullptr };
|
||||||
|
|
||||||
int buttonId;
|
int buttonId;
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace Crash
|
||||||
|
|
||||||
void signalApp() const;
|
void signalApp() const;
|
||||||
|
|
||||||
bool waitApp() const;
|
bool waitApp(bool thawMode) const;
|
||||||
|
|
||||||
bool isAppAlive() const;
|
bool isAppAlive() const;
|
||||||
|
|
||||||
|
|
|
@ -106,94 +106,96 @@ namespace Debug
|
||||||
logListener = std::move(listener);
|
logListener = std::move(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebugOutputBase : public boost::iostreams::sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual std::streamsize write(const char* str, std::streamsize size)
|
|
||||||
{
|
|
||||||
if (size <= 0)
|
|
||||||
return size;
|
|
||||||
std::string_view msg{ str, static_cast<size_t>(size) };
|
|
||||||
|
|
||||||
// Skip debug level marker
|
|
||||||
Level level = All;
|
|
||||||
if (Log::sWriteLevel)
|
|
||||||
{
|
|
||||||
level = getLevelMarker(msg[0]);
|
|
||||||
msg = msg.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
char prefix[32];
|
|
||||||
std::size_t prefixSize;
|
|
||||||
{
|
|
||||||
prefix[0] = '[';
|
|
||||||
const auto now = std::chrono::system_clock::now();
|
|
||||||
const auto time = std::chrono::system_clock::to_time_t(now);
|
|
||||||
tm time_info{};
|
|
||||||
#ifdef _WIN32
|
|
||||||
(void)localtime_s(&time_info, &time);
|
|
||||||
#else
|
|
||||||
(void)localtime_r(&time, &time_info);
|
|
||||||
#endif
|
|
||||||
prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", &time_info) + 1;
|
|
||||||
char levelLetter = " EWIVD*"[int(level)];
|
|
||||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
||||||
prefixSize += snprintf(prefix + prefixSize, sizeof(prefix) - prefixSize, ".%03u %c] ",
|
|
||||||
static_cast<unsigned>(ms % 1000), levelLetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!msg.empty())
|
|
||||||
{
|
|
||||||
if (msg[0] == 0)
|
|
||||||
break;
|
|
||||||
size_t lineSize = 1;
|
|
||||||
while (lineSize < msg.size() && msg[lineSize - 1] != '\n')
|
|
||||||
lineSize++;
|
|
||||||
writeImpl(prefix, prefixSize, level);
|
|
||||||
writeImpl(msg.data(), lineSize, level);
|
|
||||||
if (logListener)
|
|
||||||
logListener(level, std::string_view(prefix, prefixSize), std::string_view(msg.data(), lineSize));
|
|
||||||
msg = msg.substr(lineSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~DebugOutputBase() = default;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static Level getLevelMarker(char marker)
|
|
||||||
{
|
|
||||||
if (0 <= marker && static_cast<unsigned>(marker) < static_cast<unsigned>(All))
|
|
||||||
return static_cast<Level>(marker);
|
|
||||||
return All;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel)
|
|
||||||
{
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined _WIN32 && defined _DEBUG
|
|
||||||
class DebugOutput : public DebugOutputBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel)
|
|
||||||
{
|
|
||||||
// Make a copy for null termination
|
|
||||||
std::string tmp(str, static_cast<unsigned int>(size));
|
|
||||||
// Write string to Visual Studio Debug output
|
|
||||||
OutputDebugString(tmp.c_str());
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~DebugOutput() = default;
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
class DebugOutputBase : public boost::iostreams::sink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual std::streamsize write(const char* str, std::streamsize size)
|
||||||
|
{
|
||||||
|
if (size <= 0)
|
||||||
|
return size;
|
||||||
|
std::string_view msg{ str, static_cast<size_t>(size) };
|
||||||
|
|
||||||
|
// Skip debug level marker
|
||||||
|
Level level = All;
|
||||||
|
if (Log::sWriteLevel)
|
||||||
|
{
|
||||||
|
level = getLevelMarker(msg[0]);
|
||||||
|
msg = msg.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char prefix[32];
|
||||||
|
std::size_t prefixSize;
|
||||||
|
{
|
||||||
|
prefix[0] = '[';
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto time = std::chrono::system_clock::to_time_t(now);
|
||||||
|
tm time_info{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
(void)localtime_s(&time_info, &time);
|
||||||
|
#else
|
||||||
|
(void)localtime_r(&time, &time_info);
|
||||||
|
#endif
|
||||||
|
prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", &time_info) + 1;
|
||||||
|
char levelLetter = " EWIVD*"[int(level)];
|
||||||
|
const auto ms
|
||||||
|
= std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||||
|
prefixSize += snprintf(prefix + prefixSize, sizeof(prefix) - prefixSize, ".%03u %c] ",
|
||||||
|
static_cast<unsigned>(ms % 1000), levelLetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!msg.empty())
|
||||||
|
{
|
||||||
|
if (msg[0] == 0)
|
||||||
|
break;
|
||||||
|
size_t lineSize = 1;
|
||||||
|
while (lineSize < msg.size() && msg[lineSize - 1] != '\n')
|
||||||
|
lineSize++;
|
||||||
|
writeImpl(prefix, prefixSize, level);
|
||||||
|
writeImpl(msg.data(), lineSize, level);
|
||||||
|
if (logListener)
|
||||||
|
logListener(
|
||||||
|
level, std::string_view(prefix, prefixSize), std::string_view(msg.data(), lineSize));
|
||||||
|
msg = msg.substr(lineSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~DebugOutputBase() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static Level getLevelMarker(char marker)
|
||||||
|
{
|
||||||
|
if (0 <= marker && static_cast<unsigned>(marker) < static_cast<unsigned>(All))
|
||||||
|
return static_cast<Level>(marker);
|
||||||
|
return All;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel)
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined _WIN32 && defined _DEBUG
|
||||||
|
class DebugOutput : public DebugOutputBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel)
|
||||||
|
{
|
||||||
|
// Make a copy for null termination
|
||||||
|
std::string tmp(str, static_cast<unsigned int>(size));
|
||||||
|
// Write string to Visual Studio Debug output
|
||||||
|
OutputDebugString(tmp.c_str());
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~DebugOutput() = default;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
|
||||||
struct Record
|
struct Record
|
||||||
{
|
{
|
||||||
std::string mValue;
|
std::string mValue;
|
||||||
|
@ -324,22 +326,38 @@ namespace Debug
|
||||||
First mFirst;
|
First mFirst;
|
||||||
Second mSecond;
|
Second mSecond;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static std::unique_ptr<std::ostream> rawStdout = nullptr;
|
Level toLevel(std::string_view value)
|
||||||
static std::unique_ptr<std::ostream> rawStderr = nullptr;
|
{
|
||||||
static std::unique_ptr<std::mutex> rawStderrMutex = nullptr;
|
if (value == "ERROR")
|
||||||
static std::ofstream logfile;
|
return Error;
|
||||||
|
if (value == "WARNING")
|
||||||
|
return Warning;
|
||||||
|
if (value == "INFO")
|
||||||
|
return Info;
|
||||||
|
if (value == "VERBOSE")
|
||||||
|
return Verbose;
|
||||||
|
if (value == "DEBUG")
|
||||||
|
return Debug;
|
||||||
|
|
||||||
|
return Verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<std::ostream> rawStdout = nullptr;
|
||||||
|
static std::unique_ptr<std::ostream> rawStderr = nullptr;
|
||||||
|
static std::unique_ptr<std::mutex> rawStderrMutex = nullptr;
|
||||||
|
static std::ofstream logfile;
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(_DEBUG)
|
#if defined(_WIN32) && defined(_DEBUG)
|
||||||
static boost::iostreams::stream_buffer<DebugOutput> sb;
|
static boost::iostreams::stream_buffer<DebugOutput> sb;
|
||||||
#else
|
#else
|
||||||
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardOut;
|
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardOut;
|
||||||
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardErr;
|
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardErr;
|
||||||
static boost::iostreams::stream_buffer<Tee<Buffer, Coloured>> bufferedOut;
|
static boost::iostreams::stream_buffer<Tee<Buffer, Coloured>> bufferedOut;
|
||||||
static boost::iostreams::stream_buffer<Tee<Buffer, Coloured>> bufferedErr;
|
static boost::iostreams::stream_buffer<Tee<Buffer, Coloured>> bufferedErr;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream& getRawStdout()
|
std::ostream& getRawStdout()
|
||||||
{
|
{
|
||||||
|
@ -359,23 +377,19 @@ namespace Debug
|
||||||
Level getDebugLevel()
|
Level getDebugLevel()
|
||||||
{
|
{
|
||||||
if (const char* env = getenv("OPENMW_DEBUG_LEVEL"))
|
if (const char* env = getenv("OPENMW_DEBUG_LEVEL"))
|
||||||
{
|
return toLevel(env);
|
||||||
const std::string_view value(env);
|
|
||||||
if (value == "ERROR")
|
|
||||||
return Error;
|
|
||||||
if (value == "WARNING")
|
|
||||||
return Warning;
|
|
||||||
if (value == "INFO")
|
|
||||||
return Info;
|
|
||||||
if (value == "VERBOSE")
|
|
||||||
return Verbose;
|
|
||||||
if (value == "DEBUG")
|
|
||||||
return Debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Verbose;
|
return Verbose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Level getRecastMaxLogLevel()
|
||||||
|
{
|
||||||
|
if (const char* env = getenv("OPENMW_RECAST_MAX_LOG_LEVEL"))
|
||||||
|
return toLevel(env);
|
||||||
|
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
|
||||||
void setupLogging(const std::filesystem::path& logDir, std::string_view appName)
|
void setupLogging(const std::filesystem::path& logDir, std::string_view appName)
|
||||||
{
|
{
|
||||||
Log::sMinDebugLevel = getDebugLevel();
|
Log::sMinDebugLevel = getDebugLevel();
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace Debug
|
||||||
|
|
||||||
Level getDebugLevel();
|
Level getDebugLevel();
|
||||||
|
|
||||||
|
Level getRecastMaxLogLevel();
|
||||||
|
|
||||||
// Redirect cout and cerr to the log file
|
// Redirect cout and cerr to the log file
|
||||||
void setupLogging(const std::filesystem::path& logDir, std::string_view appName);
|
void setupLogging(const std::filesystem::path& logDir, std::string_view appName);
|
||||||
|
|
||||||
|
|
|
@ -453,9 +453,9 @@ namespace DetourNavigator
|
||||||
Misc::setCurrentThreadIdlePriority();
|
Misc::setCurrentThreadIdlePriority();
|
||||||
while (!mShouldStop)
|
while (!mShouldStop)
|
||||||
{
|
{
|
||||||
try
|
if (JobIt job = getNextJob(); job != mJobs.end())
|
||||||
{
|
{
|
||||||
if (JobIt job = getNextJob(); job != mJobs.end())
|
try
|
||||||
{
|
{
|
||||||
const JobStatus status = processJob(*job);
|
const JobStatus status = processJob(*job);
|
||||||
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status
|
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status
|
||||||
|
@ -480,12 +480,20 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (const std::exception& e)
|
||||||
cleanupLastUpdates();
|
{
|
||||||
|
Log(Debug::Warning) << "Failed to process navmesh job " << job->mId
|
||||||
|
<< " for worldspace=" << job->mWorldspace << " agent=" << job->mAgentBounds
|
||||||
|
<< " changedTile=(" << job->mChangedTile << ")"
|
||||||
|
<< " changeType=" << job->mChangeType
|
||||||
|
<< " by thread=" << std::this_thread::get_id() << ": " << e.what();
|
||||||
|
unlockTile(job->mId, job->mAgentBounds, job->mChangedTile);
|
||||||
|
removeJob(job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
else
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what();
|
cleanupLastUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
|
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
|
||||||
|
@ -493,7 +501,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
JobStatus AsyncNavMeshUpdater::processJob(Job& job)
|
JobStatus AsyncNavMeshUpdater::processJob(Job& job)
|
||||||
{
|
{
|
||||||
Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")"
|
Log(Debug::Debug) << "Processing job " << job.mId << " for worldspace=" << job.mWorldspace
|
||||||
|
<< " agent=" << job.mAgentBounds << ""
|
||||||
<< " changedTile=(" << job.mChangedTile << ")"
|
<< " changedTile=(" << job.mChangedTile << ")"
|
||||||
<< " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id();
|
<< " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id();
|
||||||
|
|
||||||
|
@ -543,7 +552,14 @@ namespace DetourNavigator
|
||||||
return JobStatus::Done;
|
return JobStatus::Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDebugRecastMesh(mSettings, job.mChangedTile, *recastMesh);
|
try
|
||||||
|
{
|
||||||
|
writeDebugRecastMesh(mSettings, job.mChangedTile, *recastMesh);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "Failed to write debug recast mesh: " << e.what();
|
||||||
|
}
|
||||||
|
|
||||||
NavMeshTilesCache::Value cachedNavMeshData
|
NavMeshTilesCache::Value cachedNavMeshData
|
||||||
= mNavMeshTilesCache.get(job.mAgentBounds, job.mChangedTile, *recastMesh);
|
= mNavMeshTilesCache.get(job.mAgentBounds, job.mChangedTile, *recastMesh);
|
||||||
|
@ -666,12 +682,19 @@ namespace DetourNavigator
|
||||||
mPresentTiles.insert(std::make_tuple(job.mAgentBounds, job.mChangedTile));
|
mPresentTiles.insert(std::make_tuple(job.mAgentBounds, job.mChangedTile));
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDebugNavMesh(mSettings, navMeshCacheItem, navMeshVersion);
|
try
|
||||||
|
{
|
||||||
|
writeDebugNavMesh(mSettings, navMeshCacheItem, navMeshVersion);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "Failed to write debug navmesh: " << e.what();
|
||||||
|
}
|
||||||
|
|
||||||
return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
|
return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobIt AsyncNavMeshUpdater::getNextJob()
|
JobIt AsyncNavMeshUpdater::getNextJob() noexcept
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
|
@ -746,7 +769,7 @@ namespace DetourNavigator
|
||||||
return mJobs.size();
|
return mJobs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncNavMeshUpdater::cleanupLastUpdates()
|
void AsyncNavMeshUpdater::cleanupLastUpdates() noexcept
|
||||||
{
|
{
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
|
|
@ -244,7 +244,7 @@ namespace DetourNavigator
|
||||||
inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job,
|
inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job,
|
||||||
const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh);
|
const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh);
|
||||||
|
|
||||||
JobIt getNextJob();
|
inline JobIt getNextJob() noexcept;
|
||||||
|
|
||||||
void postThreadJob(JobIt job, std::deque<JobIt>& queue);
|
void postThreadJob(JobIt job, std::deque<JobIt>& queue);
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ namespace DetourNavigator
|
||||||
|
|
||||||
inline std::size_t getTotalJobs() const;
|
inline std::size_t getTotalJobs() const;
|
||||||
|
|
||||||
void cleanupLastUpdates();
|
inline void cleanupLastUpdates() noexcept;
|
||||||
|
|
||||||
inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener);
|
inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener);
|
||||||
|
|
||||||
|
|
|
@ -523,7 +523,7 @@ namespace DetourNavigator
|
||||||
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, ESM::RefId worldspace,
|
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, ESM::RefId worldspace,
|
||||||
const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings)
|
const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings)
|
||||||
{
|
{
|
||||||
RecastContext context(worldspace, tilePosition, agentBounds);
|
RecastContext context(worldspace, tilePosition, agentBounds, recastMesh.getVersion(), settings.mMaxLogLevel);
|
||||||
|
|
||||||
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentBounds.mHalfExtents.z(), settings);
|
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentBounds.mHalfExtents.z(), settings);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "recastcontext.hpp"
|
#include "recastcontext.hpp"
|
||||||
#include "debug.hpp"
|
#include "debug.hpp"
|
||||||
|
|
||||||
#include "components/debug/debuglog.hpp"
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
@ -23,25 +23,30 @@ namespace DetourNavigator
|
||||||
return Debug::Debug;
|
return Debug::Debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string formatPrefix(
|
std::string formatPrefix(ESM::RefId worldspace, const TilePosition& tilePosition,
|
||||||
ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds)
|
const AgentBounds& agentBounds, const Version& version)
|
||||||
{
|
{
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
stream << "Worldspace: " << worldspace << "; tile position: " << tilePosition.x() << ", "
|
stream << "Worldspace: " << worldspace << "; tile position: " << tilePosition.x() << ", "
|
||||||
<< tilePosition.y() << "; agent bounds: " << agentBounds << "; ";
|
<< tilePosition.y() << "; agent bounds: " << agentBounds << "; version: " << version << "; ";
|
||||||
return stream.str();
|
return stream.str();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RecastContext::RecastContext(
|
RecastContext::RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition,
|
||||||
ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds)
|
const AgentBounds& agentBounds, const Version& version, Debug::Level maxLogLevel)
|
||||||
: mPrefix(formatPrefix(worldspace, tilePosition, agentBounds))
|
: mMaxLogLevel(maxLogLevel)
|
||||||
|
, mPrefix(formatPrefix(worldspace, tilePosition, agentBounds, version))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len)
|
void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len)
|
||||||
{
|
{
|
||||||
if (len > 0)
|
if (msg == nullptr || len <= 0)
|
||||||
Log(getLogLevel(category)) << mPrefix << std::string_view(msg, static_cast<std::size_t>(len));
|
return;
|
||||||
|
const Debug::Level level = getLogLevel(category);
|
||||||
|
if (level > mMaxLogLevel)
|
||||||
|
return;
|
||||||
|
Log(level) << mPrefix << std::string_view(msg, static_cast<std::size_t>(len));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "tileposition.hpp"
|
#include "tileposition.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/esm/refid.hpp>
|
#include <components/esm/refid.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -12,15 +13,18 @@
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
struct AgentBounds;
|
struct AgentBounds;
|
||||||
|
struct Version;
|
||||||
|
|
||||||
class RecastContext final : public rcContext
|
class RecastContext final : public rcContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds);
|
explicit RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds,
|
||||||
|
const Version& version, Debug::Level maxLogLevel);
|
||||||
|
|
||||||
const std::string& getPrefix() const { return mPrefix; }
|
const std::string& getPrefix() const { return mPrefix; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Debug::Level mMaxLogLevel;
|
||||||
std::string mPrefix;
|
std::string mPrefix;
|
||||||
|
|
||||||
void doLog(rcLogCategory category, const char* msg, int len) override;
|
void doLog(rcLogCategory category, const char* msg, int len) override;
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace DetourNavigator
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RecastSettings makeRecastSettingsFromSettingsManager()
|
RecastSettings makeRecastSettingsFromSettingsManager(Debug::Level maxLogLevel)
|
||||||
{
|
{
|
||||||
RecastSettings result;
|
RecastSettings result;
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ namespace DetourNavigator
|
||||||
result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea;
|
result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea;
|
||||||
result.mRegionMinArea = ::Settings::navigator().mRegionMinArea;
|
result.mRegionMinArea = ::Settings::navigator().mRegionMinArea;
|
||||||
result.mTileSize = ::Settings::navigator().mTileSize;
|
result.mTileSize = ::Settings::navigator().mTileSize;
|
||||||
|
result.mMaxLogLevel = maxLogLevel;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -80,11 +81,11 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings makeSettingsFromSettingsManager()
|
Settings makeSettingsFromSettingsManager(Debug::Level maxLogLevel)
|
||||||
{
|
{
|
||||||
Settings result;
|
Settings result;
|
||||||
|
|
||||||
result.mRecast = makeRecastSettingsFromSettingsManager();
|
result.mRecast = makeRecastSettingsFromSettingsManager(maxLogLevel);
|
||||||
result.mDetour = makeDetourSettingsFromSettingsManager();
|
result.mDetour = makeDetourSettingsFromSettingsManager();
|
||||||
|
|
||||||
const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour);
|
const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
|
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
|
||||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
|
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ namespace DetourNavigator
|
||||||
int mRegionMergeArea = 0;
|
int mRegionMergeArea = 0;
|
||||||
int mRegionMinArea = 0;
|
int mRegionMinArea = 0;
|
||||||
int mTileSize = 0;
|
int mTileSize = 0;
|
||||||
|
Debug::Level mMaxLogLevel = Debug::Error;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DetourSettings
|
struct DetourSettings
|
||||||
|
@ -55,7 +58,7 @@ namespace DetourNavigator
|
||||||
|
|
||||||
inline constexpr std::int64_t navMeshFormatVersion = 2;
|
inline constexpr std::int64_t navMeshFormatVersion = 2;
|
||||||
|
|
||||||
Settings makeSettingsFromSettingsManager();
|
Settings makeSettingsFromSettingsManager(Debug::Level maxLogLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include "luascripts.hpp"
|
#include "luascripts.hpp"
|
||||||
|
|
||||||
#include "components/esm3/esmreader.hpp"
|
#include <components/esm3/esmreader.hpp>
|
||||||
#include "components/esm3/esmwriter.hpp"
|
#include <components/esm3/esmwriter.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/luastateptr.hpp>
|
||||||
#include <components/lua/serialization.hpp>
|
#include <components/lua/serialization.hpp>
|
||||||
|
|
||||||
// List of all records, that are related to Lua.
|
// List of all records, that are related to Lua.
|
||||||
|
@ -102,13 +103,16 @@ void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm)
|
||||||
throw std::runtime_error("Incorrect contentFile index");
|
throw std::runtime_error("Incorrect contentFile index");
|
||||||
};
|
};
|
||||||
|
|
||||||
lua_State* L = luaL_newstate();
|
LuaUtil::LuaStatePtr state(luaL_newstate());
|
||||||
|
if (state == nullptr)
|
||||||
|
throw std::runtime_error("Failed to create Lua runtime");
|
||||||
|
|
||||||
LuaUtil::BasicSerializer serializer(adjustRefNumFn);
|
LuaUtil::BasicSerializer serializer(adjustRefNumFn);
|
||||||
|
|
||||||
auto adjustLuaData = [&](std::string& data) {
|
auto adjustLuaData = [&](std::string& data) {
|
||||||
if (data.empty())
|
if (data.empty())
|
||||||
return;
|
return;
|
||||||
sol::object luaData = LuaUtil::deserialize(L, data, &serializer);
|
sol::object luaData = LuaUtil::deserialize(state.get(), data, &serializer);
|
||||||
data = LuaUtil::serialize(luaData, &serializer);
|
data = LuaUtil::serialize(luaData, &serializer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,7 +127,6 @@ void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm)
|
||||||
refCfg.mRefnumContentFile = adjustRefNumFn(refCfg.mRefnumContentFile);
|
refCfg.mRefnumContentFile = adjustRefNumFn(refCfg.mRefnumContentFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lua_close(L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESM::LuaScriptsCfg::save(ESMWriter& esm) const
|
void ESM::LuaScriptsCfg::save(ESMWriter& esm) const
|
||||||
|
|
|
@ -73,10 +73,10 @@ namespace ESM
|
||||||
int index = getIndex();
|
int index = getIndex();
|
||||||
for (int i = 0; i < getIndex(); i++)
|
for (int i = 0; i < getIndex(); i++)
|
||||||
{
|
{
|
||||||
const ESM::ReadersCache::BusyItem reader = readers.get(static_cast<std::size_t>(i));
|
if (readers.getFileSize(static_cast<std::size_t>(i)) == 0)
|
||||||
if (reader->getFileSize() == 0)
|
|
||||||
continue; // Content file in non-ESM format
|
continue; // Content file in non-ESM format
|
||||||
const auto fnamecandidate = Files::pathToUnicodeString(reader->getName().filename());
|
const auto fnamecandidate
|
||||||
|
= Files::pathToUnicodeString(readers.getName(static_cast<std::size_t>(i)).filename());
|
||||||
if (Misc::StringUtils::ciEqual(fname, fnamecandidate))
|
if (Misc::StringUtils::ciEqual(fname, fnamecandidate))
|
||||||
{
|
{
|
||||||
index = i;
|
index = i;
|
||||||
|
|
|
@ -47,6 +47,7 @@ namespace ESM
|
||||||
{
|
{
|
||||||
it->mReader.open(*it->mName);
|
it->mReader.open(*it->mName);
|
||||||
it->mName.reset();
|
it->mName.reset();
|
||||||
|
it->mFileSize.reset();
|
||||||
}
|
}
|
||||||
mBusyItems.splice(mBusyItems.end(), mClosedItems, it);
|
mBusyItems.splice(mBusyItems.end(), mClosedItems, it);
|
||||||
break;
|
break;
|
||||||
|
@ -57,6 +58,46 @@ namespace ESM
|
||||||
return BusyItem(*this, it);
|
return BusyItem(*this, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& ReadersCache::getName(std::size_t index) const
|
||||||
|
{
|
||||||
|
const auto indexIt = mIndex.find(index);
|
||||||
|
if (indexIt == mIndex.end())
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " has not been created yet");
|
||||||
|
switch (indexIt->second->mState)
|
||||||
|
{
|
||||||
|
case State::Busy:
|
||||||
|
case State::Free:
|
||||||
|
return indexIt->second->mReader.getName();
|
||||||
|
case State::Closed:
|
||||||
|
if (indexIt->second->mName)
|
||||||
|
return *indexIt->second->mName;
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " has forgotten its filename");
|
||||||
|
default:
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " in unknown state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ReadersCache::getFileSize(std::size_t index)
|
||||||
|
{
|
||||||
|
const auto indexIt = mIndex.find(index);
|
||||||
|
if (indexIt == mIndex.end())
|
||||||
|
return 0;
|
||||||
|
switch (indexIt->second->mState)
|
||||||
|
{
|
||||||
|
case State::Busy:
|
||||||
|
case State::Free:
|
||||||
|
if (!indexIt->second->mReader.getName().empty())
|
||||||
|
return indexIt->second->mReader.getFileSize();
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " has not been opened yet");
|
||||||
|
case State::Closed:
|
||||||
|
if (indexIt->second->mFileSize)
|
||||||
|
return *indexIt->second->mFileSize;
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " has forgotten its file size");
|
||||||
|
default:
|
||||||
|
throw std::logic_error("ESMReader at index " + std::to_string(index) + " in unknown state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ReadersCache::closeExtraReaders()
|
void ReadersCache::closeExtraReaders()
|
||||||
{
|
{
|
||||||
while (!mFreeItems.empty() && mBusyItems.size() + mFreeItems.size() + 1 > mCapacity)
|
while (!mFreeItems.empty() && mBusyItems.size() + mFreeItems.size() + 1 > mCapacity)
|
||||||
|
@ -65,6 +106,7 @@ namespace ESM
|
||||||
if (it->mReader.isOpen())
|
if (it->mReader.isOpen())
|
||||||
{
|
{
|
||||||
it->mName = it->mReader.getName();
|
it->mName = it->mReader.getName();
|
||||||
|
it->mFileSize = it->mReader.getFileSize();
|
||||||
it->mReader.close();
|
it->mReader.close();
|
||||||
}
|
}
|
||||||
mClosedItems.splice(mClosedItems.end(), mFreeItems, it);
|
mClosedItems.splice(mClosedItems.end(), mFreeItems, it);
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace ESM
|
||||||
State mState = State::Busy;
|
State mState = State::Busy;
|
||||||
ESMReader mReader;
|
ESMReader mReader;
|
||||||
std::optional<std::filesystem::path> mName;
|
std::optional<std::filesystem::path> mName;
|
||||||
|
std::optional<std::size_t> mFileSize;
|
||||||
|
|
||||||
Item() = default;
|
Item() = default;
|
||||||
};
|
};
|
||||||
|
@ -55,6 +56,10 @@ namespace ESM
|
||||||
|
|
||||||
BusyItem get(std::size_t index);
|
BusyItem get(std::size_t index);
|
||||||
|
|
||||||
|
const std::filesystem::path& getName(std::size_t index) const;
|
||||||
|
|
||||||
|
std::size_t getFileSize(std::size_t index);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -114,6 +114,9 @@ void ESM4::Quest::load(ESM4::Reader& reader)
|
||||||
case ESM::fourCC("NNAM"): // FO3
|
case ESM::fourCC("NNAM"): // FO3
|
||||||
case ESM::fourCC("QOBJ"): // FO3
|
case ESM::fourCC("QOBJ"): // FO3
|
||||||
case ESM::fourCC("NAM0"): // FO3
|
case ESM::fourCC("NAM0"): // FO3
|
||||||
|
case ESM::fourCC("SLSD"): // FO3
|
||||||
|
case ESM::fourCC("SCVR"): // FO3
|
||||||
|
case ESM::fourCC("SCRV"): // FO3
|
||||||
case ESM::fourCC("ANAM"): // TES5
|
case ESM::fourCC("ANAM"): // TES5
|
||||||
case ESM::fourCC("DNAM"): // TES5
|
case ESM::fourCC("DNAM"): // TES5
|
||||||
case ESM::fourCC("ENAM"): // TES5
|
case ESM::fourCC("ENAM"): // TES5
|
||||||
|
|
|
@ -24,8 +24,6 @@ namespace Fallback
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses and validates a fallback map from boost program_options.
|
// Parses and validates a fallback map from boost program_options.
|
||||||
// Note: for boost to pick up the validate function, you need to pull in the namespace e.g.
|
|
||||||
// by using namespace Fallback;
|
|
||||||
void validate(boost::any& v, std::vector<std::string> const& tokens, FallbackMap*, int);
|
void validate(boost::any& v, std::vector<std::string> const& tokens, FallbackMap*, int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,12 +36,14 @@ namespace Files
|
||||||
{
|
{
|
||||||
std::filesystem::path userPath = std::filesystem::current_path();
|
std::filesystem::path userPath = std::filesystem::current_path();
|
||||||
|
|
||||||
WCHAR path[MAX_PATH + 1] = {};
|
PWSTR cString;
|
||||||
|
HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &cString);
|
||||||
|
if (SUCCEEDED(result))
|
||||||
|
userPath = std::filesystem::path(cString);
|
||||||
|
else
|
||||||
|
Log(Debug::Error) << "Error " << result << " when getting Documents path";
|
||||||
|
|
||||||
if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr, 0, path)))
|
CoTaskMemFree(cString);
|
||||||
{
|
|
||||||
userPath = std::filesystem::path(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userPath / "My Games" / mName;
|
return userPath / "My Games" / mName;
|
||||||
}
|
}
|
||||||
|
@ -54,14 +56,19 @@ namespace Files
|
||||||
|
|
||||||
std::filesystem::path WindowsPath::getGlobalConfigPath() const
|
std::filesystem::path WindowsPath::getGlobalConfigPath() const
|
||||||
{
|
{
|
||||||
|
// The concept of a global config path is absurd on Windows.
|
||||||
|
// Always use local config instead.
|
||||||
|
// The virtual base class requires that we provide this, though.
|
||||||
std::filesystem::path globalPath = std::filesystem::current_path();
|
std::filesystem::path globalPath = std::filesystem::current_path();
|
||||||
|
|
||||||
WCHAR path[MAX_PATH + 1] = {};
|
PWSTR cString;
|
||||||
|
HRESULT result = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &cString);
|
||||||
|
if (SUCCEEDED(result))
|
||||||
|
globalPath = std::filesystem::path(cString);
|
||||||
|
else
|
||||||
|
Log(Debug::Error) << "Error " << result << " when getting Program Files path";
|
||||||
|
|
||||||
if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, nullptr, 0, path)))
|
CoTaskMemFree(cString);
|
||||||
{
|
|
||||||
globalPath = std::filesystem::path(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return globalPath / mName;
|
return globalPath / mName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,9 +227,10 @@ namespace
|
||||||
namespace Gui
|
namespace Gui
|
||||||
{
|
{
|
||||||
|
|
||||||
FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor)
|
FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor, bool exportFonts)
|
||||||
: mVFS(vfs)
|
: mVFS(vfs)
|
||||||
, mScalingFactor(scalingFactor)
|
, mScalingFactor(scalingFactor)
|
||||||
|
, mExportFonts(exportFonts)
|
||||||
{
|
{
|
||||||
if (encoding == ToUTF8::WINDOWS_1252)
|
if (encoding == ToUTF8::WINDOWS_1252)
|
||||||
mEncoding = ToUTF8::CP437;
|
mEncoding = ToUTF8::CP437;
|
||||||
|
@ -407,7 +408,8 @@ namespace Gui
|
||||||
file.reset();
|
file.reset();
|
||||||
|
|
||||||
// Create the font texture
|
// Create the font texture
|
||||||
std::string bitmapFilename = "fonts/" + std::string(name_) + ".tex";
|
const std::string name(name_);
|
||||||
|
const std::string bitmapFilename = "fonts/" + name + ".tex";
|
||||||
|
|
||||||
Files::IStreamPtr bitmapFile = mVFS->get(bitmapFilename);
|
Files::IStreamPtr bitmapFile = mVFS->get(bitmapFilename);
|
||||||
|
|
||||||
|
@ -429,6 +431,19 @@ namespace Gui
|
||||||
<< bitmapFile->gcount() << "/" << (width * height * 4) << " bytes)";
|
<< bitmapFile->gcount() << "/" << (width * height * 4) << " bytes)";
|
||||||
bitmapFile.reset();
|
bitmapFile.reset();
|
||||||
|
|
||||||
|
if (mExportFonts)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Image> image = new osg::Image;
|
||||||
|
image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
|
||||||
|
assert(image->isDataContiguous());
|
||||||
|
memcpy(image->data(), textureData.data(), textureData.size());
|
||||||
|
// Convert to OpenGL origin for sensible output
|
||||||
|
image->flipVertical();
|
||||||
|
|
||||||
|
Log(Debug::Info) << "Writing " << name + ".png";
|
||||||
|
osgDB::writeImageFile(*image, name + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture(bitmapFilename);
|
MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture(bitmapFilename);
|
||||||
tex->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8);
|
tex->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8);
|
||||||
unsigned char* texData = reinterpret_cast<unsigned char*>(tex->lock(MyGUI::TextureUsage::Write));
|
unsigned char* texData = reinterpret_cast<unsigned char*>(tex->lock(MyGUI::TextureUsage::Write));
|
||||||
|
@ -626,6 +641,13 @@ namespace Gui
|
||||||
code->addAttribute("size", "0 0");
|
code->addAttribute("size", "0 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mExportFonts)
|
||||||
|
{
|
||||||
|
Log(Debug::Info) << "Writing " << name + ".xml";
|
||||||
|
xmlDocument.createDeclaration();
|
||||||
|
xmlDocument.save(name + ".xml");
|
||||||
|
}
|
||||||
|
|
||||||
// Register the font with MyGUI
|
// Register the font with MyGUI
|
||||||
MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
|
MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
|
||||||
MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont"));
|
MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont"));
|
||||||
|
|
|
@ -25,7 +25,8 @@ namespace Gui
|
||||||
class FontLoader
|
class FontLoader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor);
|
/// @param exportFonts export the converted fonts (Images and XML with glyph metrics) to files?
|
||||||
|
FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor, bool exportFonts);
|
||||||
|
|
||||||
void overrideLineHeight(MyGUI::xml::ElementPtr _node, std::string_view _file, MyGUI::Version _version);
|
void overrideLineHeight(MyGUI::xml::ElementPtr _node, std::string_view _file, MyGUI::Version _version);
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ namespace Gui
|
||||||
ToUTF8::FromType mEncoding;
|
ToUTF8::FromType mEncoding;
|
||||||
const VFS::Manager* mVFS;
|
const VFS::Manager* mVFS;
|
||||||
float mScalingFactor;
|
float mScalingFactor;
|
||||||
|
bool mExportFonts;
|
||||||
|
|
||||||
void loadFonts();
|
void loadFonts();
|
||||||
void loadFont(const std::string& fontName, const std::string& fontId);
|
void loadFont(const std::string& fontName, const std::string& fontId);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <components/files/conversion.hpp>
|
#include <components/files/conversion.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
|
#include "luastateptr.hpp"
|
||||||
#include "scriptscontainer.hpp"
|
#include "scriptscontainer.hpp"
|
||||||
#include "utf8.hpp"
|
#include "utf8.hpp"
|
||||||
|
|
||||||
|
@ -151,37 +152,37 @@ namespace LuaUtil
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_State* LuaState::createLuaRuntime(LuaState* luaState)
|
LuaStatePtr LuaState::createLuaRuntime(LuaState* luaState)
|
||||||
{
|
{
|
||||||
if (sProfilerEnabled)
|
if (sProfilerEnabled)
|
||||||
{
|
{
|
||||||
Log(Debug::Info) << "Initializing LuaUtil::LuaState with profiler";
|
Log(Debug::Info) << "Initializing LuaUtil::LuaState with profiler";
|
||||||
lua_State* L = lua_newstate(&trackingAllocator, luaState);
|
LuaStatePtr state(lua_newstate(&trackingAllocator, luaState));
|
||||||
if (L)
|
if (state != nullptr)
|
||||||
return L;
|
return state;
|
||||||
else
|
sProfilerEnabled = false;
|
||||||
{
|
Log(Debug::Error) << "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler";
|
||||||
sProfilerEnabled = false;
|
|
||||||
Log(Debug::Error)
|
|
||||||
<< "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Log(Debug::Info) << "Initializing LuaUtil::LuaState without profiler";
|
Log(Debug::Info) << "Initializing LuaUtil::LuaState without profiler";
|
||||||
lua_State* L = luaL_newstate();
|
LuaStatePtr state(luaL_newstate());
|
||||||
if (!L)
|
if (state == nullptr)
|
||||||
throw std::runtime_error("Can't create Lua runtime");
|
throw std::runtime_error("Failed to create Lua runtime");
|
||||||
return L;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings)
|
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings)
|
||||||
: mSettings(settings)
|
: mSettings(settings)
|
||||||
, mLuaHolder(createLuaRuntime(this))
|
, mLuaState([&] {
|
||||||
, mSol(mLuaHolder.get())
|
LuaStatePtr state = createLuaRuntime(this);
|
||||||
|
sol::set_default_state(state.get());
|
||||||
|
return state;
|
||||||
|
}())
|
||||||
|
, mSol(mLuaState.get())
|
||||||
, mConf(conf)
|
, mConf(conf)
|
||||||
, mVFS(vfs)
|
, mVFS(vfs)
|
||||||
{
|
{
|
||||||
if (sProfilerEnabled)
|
if (sProfilerEnabled)
|
||||||
lua_sethook(mLuaHolder.get(), &countHook, LUA_MASKCOUNT, countHookStep);
|
lua_sethook(mLuaState.get(), &countHook, LUA_MASKCOUNT, countHookStep);
|
||||||
|
|
||||||
protectedCall([&](LuaView& view) {
|
protectedCall([&](LuaView& view) {
|
||||||
auto& sol = view.sol();
|
auto& sol = view.sol();
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <components/vfs/pathutil.hpp>
|
#include <components/vfs/pathutil.hpp>
|
||||||
|
|
||||||
#include "configuration.hpp"
|
#include "configuration.hpp"
|
||||||
|
#include "luastateptr.hpp"
|
||||||
|
|
||||||
namespace VFS
|
namespace VFS
|
||||||
{
|
{
|
||||||
|
@ -188,7 +189,7 @@ namespace LuaUtil
|
||||||
static void countHook(lua_State* L, lua_Debug* ar);
|
static void countHook(lua_State* L, lua_Debug* ar);
|
||||||
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
|
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
|
||||||
|
|
||||||
lua_State* createLuaRuntime(LuaState* luaState);
|
static LuaStatePtr createLuaRuntime(LuaState* luaState);
|
||||||
|
|
||||||
struct AllocOwner
|
struct AllocOwner
|
||||||
{
|
{
|
||||||
|
@ -206,25 +207,8 @@ namespace LuaUtil
|
||||||
uint64_t mSmallAllocMemoryUsage = 0;
|
uint64_t mSmallAllocMemoryUsage = 0;
|
||||||
std::vector<int64_t> mMemoryUsage;
|
std::vector<int64_t> mMemoryUsage;
|
||||||
|
|
||||||
class LuaStateHolder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LuaStateHolder(lua_State* L)
|
|
||||||
: L(L)
|
|
||||||
{
|
|
||||||
sol::set_default_state(L);
|
|
||||||
}
|
|
||||||
~LuaStateHolder() { lua_close(L); }
|
|
||||||
LuaStateHolder(const LuaStateHolder&) = delete;
|
|
||||||
LuaStateHolder(LuaStateHolder&&) = delete;
|
|
||||||
lua_State* get() { return L; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
lua_State* L;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Must be declared before mSol and all sol-related objects. Then on exit it will be destructed the last.
|
// Must be declared before mSol and all sol-related objects. Then on exit it will be destructed the last.
|
||||||
LuaStateHolder mLuaHolder;
|
LuaStatePtr mLuaState;
|
||||||
|
|
||||||
sol::state_view mSol;
|
sol::state_view mSol;
|
||||||
const ScriptsConfiguration* mConf;
|
const ScriptsConfiguration* mConf;
|
||||||
|
|
18
components/lua/luastateptr.hpp
Normal file
18
components/lua/luastateptr.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_LUA_LUASTATEPTR_H
|
||||||
|
#define OPENMW_COMPONENTS_LUA_LUASTATEPTR_H
|
||||||
|
|
||||||
|
#include <sol/state.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace LuaUtil
|
||||||
|
{
|
||||||
|
struct CloseLuaState
|
||||||
|
{
|
||||||
|
void operator()(lua_State* state) noexcept { lua_close(state); }
|
||||||
|
};
|
||||||
|
|
||||||
|
using LuaStatePtr = std::unique_ptr<lua_State, CloseLuaState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -391,6 +391,7 @@ namespace NifOsg
|
||||||
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
||||||
skel->setStateSet(created->getStateSet());
|
skel->setStateSet(created->getStateSet());
|
||||||
skel->setName(created->getName());
|
skel->setName(created->getName());
|
||||||
|
skel->setUserDataContainer(created->getUserDataContainer());
|
||||||
for (unsigned int i = 0; i < created->getNumChildren(); ++i)
|
for (unsigned int i = 0; i < created->getNumChildren(); ++i)
|
||||||
skel->addChild(created->getChild(i));
|
skel->addChild(created->getChild(i));
|
||||||
created->removeChildren(0, created->getNumChildren());
|
created->removeChildren(0, created->getNumChildren());
|
||||||
|
|
|
@ -119,7 +119,8 @@ namespace SceneUtil
|
||||||
osg::ref_ptr<osg::Light> light(new osg::Light);
|
osg::ref_ptr<osg::Light> light(new osg::Light);
|
||||||
lightSource->setNodeMask(lightMask);
|
lightSource->setNodeMask(lightMask);
|
||||||
|
|
||||||
float radius = esmLight.mRadius;
|
// The minimum scene light radius is 16 in Morrowind
|
||||||
|
const float radius = std::max(esmLight.mRadius, 16.f);
|
||||||
lightSource->setRadius(radius);
|
lightSource->setRadius(radius);
|
||||||
|
|
||||||
configureLight(light, radius, isExterior);
|
configureLight(light, radius, isExterior);
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
#define STEREO_FRUSTUM_H
|
#define STEREO_FRUSTUM_H
|
||||||
|
|
||||||
#include <osg/BoundingBox>
|
#include <osg/BoundingBox>
|
||||||
#include <osg/Camera>
|
|
||||||
#include <osg/Matrix>
|
#include <osg/Matrix>
|
||||||
#include <osg/StateSet>
|
|
||||||
#include <osg/Vec3>
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -13,6 +10,7 @@
|
||||||
|
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
|
class Camera;
|
||||||
class FrameBufferObject;
|
class FrameBufferObject;
|
||||||
class Texture2D;
|
class Texture2D;
|
||||||
class Texture2DMultisample;
|
class Texture2DMultisample;
|
||||||
|
@ -24,7 +22,7 @@ namespace osgViewer
|
||||||
class Viewer;
|
class Viewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace usgUtil
|
namespace osgUtil
|
||||||
{
|
{
|
||||||
class CullVisitor;
|
class CullVisitor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef OPENMW_COMPONENTS_TESTING_UTIL_H
|
#ifndef OPENMW_COMPONENTS_TESTING_UTIL_H
|
||||||
#define OPENMW_COMPONENTS_TESTING_UTIL_H
|
#define OPENMW_COMPONENTS_TESTING_UTIL_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -14,26 +15,37 @@
|
||||||
|
|
||||||
namespace TestingOpenMW
|
namespace TestingOpenMW
|
||||||
{
|
{
|
||||||
inline std::filesystem::path outputFilePath(const std::string name)
|
inline std::filesystem::path outputDir()
|
||||||
{
|
{
|
||||||
std::filesystem::path dir("tests_output");
|
static const std::string run
|
||||||
std::filesystem::create_directory(dir);
|
= std::to_string(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
|
||||||
|
std::filesystem::path dir = std::filesystem::temp_directory_path() / "openmw" / "tests" / run;
|
||||||
|
std::filesystem::create_directories(dir);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::filesystem::path outputFilePath(std::string_view name)
|
||||||
|
{
|
||||||
|
std::filesystem::path dir = outputDir();
|
||||||
return dir / Misc::StringUtils::stringToU8String(name);
|
return dir / Misc::StringUtils::stringToU8String(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::filesystem::path outputDirPath(const std::filesystem::path& subpath)
|
||||||
|
{
|
||||||
|
std::filesystem::path path = outputDir();
|
||||||
|
path /= subpath;
|
||||||
|
std::filesystem::create_directories(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::filesystem::path outputFilePathWithSubDir(const std::filesystem::path& subpath)
|
inline std::filesystem::path outputFilePathWithSubDir(const std::filesystem::path& subpath)
|
||||||
{
|
{
|
||||||
std::filesystem::path path("tests_output");
|
std::filesystem::path path = outputDir();
|
||||||
path /= subpath;
|
path /= subpath;
|
||||||
std::filesystem::create_directories(path.parent_path());
|
std::filesystem::create_directories(path.parent_path());
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::filesystem::path temporaryFilePath(const std::string name)
|
|
||||||
{
|
|
||||||
return std::filesystem::temp_directory_path() / name;
|
|
||||||
}
|
|
||||||
|
|
||||||
class VFSTestFile : public VFS::File
|
class VFSTestFile : public VFS::File
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -28,13 +28,8 @@ A `Launchpad PPA <https://launchpad.net/~openmw/+archive/openmw>`_ is available.
|
||||||
Add it and install OpenMW::
|
Add it and install OpenMW::
|
||||||
|
|
||||||
$ sudo add-apt-repository ppa:openmw/openmw
|
$ sudo add-apt-repository ppa:openmw/openmw
|
||||||
$ sudo apt-get update
|
$ sudo apt update
|
||||||
$ sudo apt-get install openmw openmw-launcher
|
$ sudo apt install openmw
|
||||||
|
|
||||||
.. note::
|
|
||||||
OpenMW-CS must be installed separately by typing::
|
|
||||||
|
|
||||||
$ sudo apt-get install openmw-cs
|
|
||||||
|
|
||||||
The Arch Linux Way
|
The Arch Linux Way
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -15,6 +15,8 @@ Morrowind .fnt fonts
|
||||||
Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format.
|
Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format.
|
||||||
To our knowledge, the format is undocumented. OpenMW can load this format and convert it on the fly into something usable
|
To our knowledge, the format is undocumented. OpenMW can load this format and convert it on the fly into something usable
|
||||||
(see font loader `source code <https://gitlab.com/OpenMW/openmw/blob/master/components/fontloader/fontloader.cpp>`_).
|
(see font loader `source code <https://gitlab.com/OpenMW/openmw/blob/master/components/fontloader/fontloader.cpp>`_).
|
||||||
|
You can use --export-fonts command line option to write the converted font
|
||||||
|
(a PNG image and an XML file describing the position of each glyph in the image) to the current directory.
|
||||||
|
|
||||||
They can be used instead of TrueType fonts if needed by specifying their ``.fnt`` files names in the ``openmw.cfg``. For example:
|
They can be used instead of TrueType fonts if needed by specifying their ``.fnt`` files names in the ``openmw.cfg``. For example:
|
||||||
|
|
||||||
|
|
3
extern/.clang-tidy
vendored
Normal file
3
extern/.clang-tidy
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Checks: >-
|
||||||
|
-clang-analyzer-core.NullDereference,
|
||||||
|
-clang-analyzer-cplusplus.NewDelete
|
|
@ -2,13 +2,13 @@
|
||||||
<Resource type="ResourceTrueTypeFont">
|
<Resource type="ResourceTrueTypeFont">
|
||||||
<Property key="Source" value="MysticCards.ttf"/>
|
<Property key="Source" value="MysticCards.ttf"/>
|
||||||
<Property key="Antialias" value="false"/>
|
<Property key="Antialias" value="false"/>
|
||||||
<Property key="SubstituteCode" value="63"/>
|
<Property key="SubstituteCode" value="95"/>
|
||||||
<Property key="TabWidth" value="8"/>
|
<Property key="TabWidth" value="8"/>
|
||||||
<Property key="OffsetHeight" value="0"/>
|
<Property key="OffsetHeight" value="0"/>
|
||||||
<Property key="Resolution" value="70"/>
|
<Property key="Resolution" value="70"/>
|
||||||
<Codes>
|
<Codes>
|
||||||
<Code range="33 126"/>
|
<Code range="33 126"/>
|
||||||
<Code range="161"/>
|
<Code range="160 161"/>
|
||||||
<Code range="173"/>
|
<Code range="173"/>
|
||||||
<Code range="175 177"/>
|
<Code range="175 177"/>
|
||||||
<Code range="180"/>
|
<Code range="180"/>
|
||||||
|
|
Binary file not shown.
|
@ -24,7 +24,7 @@ return {
|
||||||
interface = {
|
interface = {
|
||||||
--- Interface version
|
--- Interface version
|
||||||
-- @field [parent=#Crimes] #number version
|
-- @field [parent=#Crimes] #number version
|
||||||
version = 1,
|
version = 2,
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Commits a crime as if done through an in-game action. Can only be used in global context.
|
-- Commits a crime as if done through an in-game action. Can only be used in global context.
|
||||||
|
@ -42,7 +42,7 @@ return {
|
||||||
"faction id passed to commitCrime must be a string or nil")
|
"faction id passed to commitCrime must be a string or nil")
|
||||||
assert(type(options.arg) == "number" or options.arg == nil,
|
assert(type(options.arg) == "number" or options.arg == nil,
|
||||||
"arg value passed to commitCrime must be a number or nil")
|
"arg value passed to commitCrime must be a number or nil")
|
||||||
assert(type(options.victimAware) == "number" or options.victimAware == nil,
|
assert(type(options.victimAware) == "boolean" or options.victimAware == nil,
|
||||||
"victimAware value passed to commitCrime must be a boolean or nil")
|
"victimAware value passed to commitCrime must be a boolean or nil")
|
||||||
|
|
||||||
assert(options.type ~= nil, "crime type passed to commitCrime cannot be nil")
|
assert(options.type ~= nil, "crime type passed to commitCrime cannot be nil")
|
||||||
|
|
|
@ -221,14 +221,14 @@
|
||||||
-- Can be used only in local scripts on self. Can also be evoked by sending an AddVfx event to the target actor.
|
-- Can be used only in local scripts on self. Can also be evoked by sending an AddVfx event to the target actor.
|
||||||
-- @function [parent=#animation] addVfx
|
-- @function [parent=#animation] addVfx
|
||||||
-- @param openmw.core#GameObject actor
|
-- @param openmw.core#GameObject actor
|
||||||
-- @param #string model #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar)
|
-- @param #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar)
|
||||||
-- @param #table options optional table of parameters. Can contain:
|
-- @param #table options optional table of parameters. Can contain:
|
||||||
--
|
--
|
||||||
-- * `loop` - boolean, if true the effect will loop until removed (default: 0).
|
-- * `loop` - boolean, if true the effect will loop until removed (default: false).
|
||||||
-- * `boneName` - name of the bone to attach the vfx to. (default: "")
|
-- * `boneName` - name of the bone to attach the vfx to. (default: "")
|
||||||
-- * `particleTextureOverride` - name of the particle texture to use. (default: "")
|
-- * `particleTextureOverride` - name of the particle texture to use. (default: "")
|
||||||
-- * `vfxId` - a string ID that can be used to remove the effect later, using #removeVfx, and to avoid duplicate effects. The default value of "" can have duplicates. To avoid interaction with the engine, use unique identifiers unrelated to magic effect IDs. The engine uses this identifier to add and remove magic effects based on what effects are active on the actor. If this is set equal to the @{openmw.core#MagicEffectId} identifier of the magic effect being added, for example core.magic.EFFECT_TYPE.FireDamage, then the engine will remove it once the fire damage effect on the actor reaches 0. (Default: "").
|
-- * `vfxId` - a string ID that can be used to remove the effect later, using #removeVfx, and to avoid duplicate effects. The default value of "" can have duplicates. To avoid interaction with the engine, use unique identifiers unrelated to magic effect IDs. The engine uses this identifier to add and remove magic effects based on what effects are active on the actor. If this is set equal to the @{openmw.core#MagicEffectId} identifier of the magic effect being added, for example core.magic.EFFECT_TYPE.FireDamage, then the engine will remove it once the fire damage effect on the actor reaches 0. (Default: "").
|
||||||
-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: 1)
|
-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: true)
|
||||||
--
|
--
|
||||||
-- @usage local mgef = core.magic.effects.records[myEffectName]
|
-- @usage local mgef = core.magic.effects.records[myEffectName]
|
||||||
-- anim.addVfx(self, 'VFX_Hands', {boneName = 'Bip01 L Hand', particleTextureOverride = mgef.particle, loop = mgef.continuousVfx, vfxId = mgef.id..'_myuniquenamehere'})
|
-- anim.addVfx(self, 'VFX_Hands', {boneName = 'Bip01 L Hand', particleTextureOverride = mgef.particle, loop = mgef.continuousVfx, vfxId = mgef.id..'_myuniquenamehere'})
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
-- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE
|
-- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Result of raycasing
|
-- Result of raycasting
|
||||||
-- @type RayCastingResult
|
-- @type RayCastingResult
|
||||||
-- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false)
|
-- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false)
|
||||||
-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitPos Position of the collision point (nil if no collision)
|
-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitPos Position of the collision point (nil if no collision)
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
---
|
---
|
||||||
-- A table of parameters for @{#nearby.castRay}
|
-- A table of parameters for @{#nearby.castRay}
|
||||||
-- @type CastRayOptions
|
-- @type CastRayOptions
|
||||||
-- @field openmw.core#GameObject ignore An object to ignore (specify here the source of the ray)
|
-- @field #any ignore An @{openmw.core#GameObject} or @{openmw.core#ObjectList} to ignore (specify here the source of the ray, or other objects which should not collide)
|
||||||
-- @field #number collisionType Object types to work with (see @{openmw.nearby#COLLISION_TYPE})
|
-- @field #number collisionType Object types to work with (see @{openmw.nearby#COLLISION_TYPE})
|
||||||
-- @field #number radius The radius of the ray (zero by default). If not zero then castRay actually casts a sphere with given radius.
|
-- @field #number radius The radius of the ray (zero by default). If not zero then castRay actually casts a sphere with given radius.
|
||||||
-- NOTE: currently `ignore` is not supported if `radius>0`.
|
-- NOTE: currently `ignore` is not supported if `radius>0`.
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
---
|
---
|
||||||
-- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay}
|
-- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay}
|
||||||
-- @type CastRenderingRayOptions
|
-- @type CastRenderingRayOptions
|
||||||
-- @field #table ignore A list of @{openmw.core#GameObject} to ignore while doing the ray cast
|
-- @field #any ignore A @{openmw.core#GameObject} or @{openmw.core#ObjectList} to ignore while doing the ray cast
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Cast ray from one point to another and find the first visual intersection with anything in the scene.
|
-- Cast ray from one point to another and find the first visual intersection with anything in the scene.
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
-- @function [parent=#nearby] castRenderingRay
|
-- @function [parent=#nearby] castRenderingRay
|
||||||
-- @param openmw.util#Vector3 from Start point of the ray.
|
-- @param openmw.util#Vector3 from Start point of the ray.
|
||||||
-- @param openmw.util#Vector3 to End point of the ray.
|
-- @param openmw.util#Vector3 to End point of the ray.
|
||||||
-- @param #CastRenderingRayOptions
|
-- @param #CastRenderingRayOptions options An optional table with additional optional arguments
|
||||||
-- @return #RayCastingResult
|
-- @return #RayCastingResult
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
-- @field [parent=#ActorControls] #boolean run true - run, false - walk
|
-- @field [parent=#ActorControls] #boolean run true - run, false - walk
|
||||||
-- @field [parent=#ActorControls] #boolean sneak If true - sneak
|
-- @field [parent=#ActorControls] #boolean sneak If true - sneak
|
||||||
-- @field [parent=#ActorControls] #boolean jump If true - initiate a jump
|
-- @field [parent=#ActorControls] #boolean jump If true - initiate a jump
|
||||||
-- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead.
|
-- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @{#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead.
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Enables or disables standard AI (enabled by default).
|
-- Enables or disables standard AI (enabled by default).
|
||||||
|
|
|
@ -1123,7 +1123,7 @@
|
||||||
-- @field #string id The record ID of the NPC
|
-- @field #string id The record ID of the NPC
|
||||||
-- @field #string name
|
-- @field #string name
|
||||||
-- @field #string race
|
-- @field #string race
|
||||||
-- @field #string class Name of the NPC's class (e. g. Acrobat)
|
-- @field #string class ID of the NPC's class (e.g. acrobat)
|
||||||
-- @field #string model Path to the model associated with this NPC, used for animations.
|
-- @field #string model Path to the model associated with this NPC, used for animations.
|
||||||
-- @field #string mwscript MWScript on this NPC (can be nil)
|
-- @field #string mwscript MWScript on this NPC (can be nil)
|
||||||
-- @field #string hair Path to the hair body part model
|
-- @field #string hair Path to the hair body part model
|
||||||
|
|
|
@ -7,7 +7,7 @@ local vfs = require('openmw.vfs')
|
||||||
local world = require('openmw.world')
|
local world = require('openmw.world')
|
||||||
local I = require('openmw.interfaces')
|
local I = require('openmw.interfaces')
|
||||||
|
|
||||||
local function testTimers()
|
testing.registerGlobalTest('timers', function()
|
||||||
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
||||||
testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result')
|
testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result')
|
||||||
|
|
||||||
|
@ -39,9 +39,10 @@ local function testTimers()
|
||||||
testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed')
|
testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed')
|
||||||
testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed')
|
testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed')
|
||||||
testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
|
testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testTeleport()
|
testing.registerGlobalTest('teleport', function()
|
||||||
|
local player = world.players[1]
|
||||||
player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90)))
|
player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90)))
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
testing.expect(player.cell.isExterior, 'teleport to exterior failed')
|
testing.expect(player.cell.isExterior, 'teleport to exterior failed')
|
||||||
|
@ -71,16 +72,16 @@ local function testTeleport()
|
||||||
testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting')
|
testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting')
|
||||||
testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting')
|
testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting')
|
||||||
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'teleporting changes rotation')
|
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'teleporting changes rotation')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testGetGMST()
|
testing.registerGlobalTest('getGMST', function()
|
||||||
testing.expectEqual(core.getGMST('non-existed gmst'), nil)
|
testing.expectEqual(core.getGMST('non-existed gmst'), nil)
|
||||||
testing.expectEqual(core.getGMST('Water_RippleFrameCount'), 4)
|
testing.expectEqual(core.getGMST('Water_RippleFrameCount'), 4)
|
||||||
testing.expectEqual(core.getGMST('Inventory_DirectionalDiffuseR'), 0.5)
|
testing.expectEqual(core.getGMST('Inventory_DirectionalDiffuseR'), 0.5)
|
||||||
testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something')
|
testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testMWScript()
|
testing.registerGlobalTest('MWScript', function()
|
||||||
local variableStoreCount = 18
|
local variableStoreCount = 18
|
||||||
local variableStore = world.mwscript.getGlobalVariables(player)
|
local variableStore = world.mwscript.getGlobalVariables(player)
|
||||||
testing.expectEqual(variableStoreCount, #variableStore)
|
testing.expectEqual(variableStoreCount, #variableStore)
|
||||||
|
@ -100,7 +101,7 @@ local function testMWScript()
|
||||||
indexCheck = indexCheck + 1
|
indexCheck = indexCheck + 1
|
||||||
end
|
end
|
||||||
testing.expectEqual(variableStoreCount, indexCheck)
|
testing.expectEqual(variableStoreCount, indexCheck)
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testRecordStore(store, storeName, skipPairs)
|
local function testRecordStore(store, storeName, skipPairs)
|
||||||
testing.expect(store.records)
|
testing.expect(store.records)
|
||||||
|
@ -121,7 +122,7 @@ local function testRecordStore(store, storeName, skipPairs)
|
||||||
testing.expectEqual(status, true, storeName)
|
testing.expectEqual(status, true, storeName)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function testRecordStores()
|
testing.registerGlobalTest('record stores', function()
|
||||||
for key, type in pairs(types) do
|
for key, type in pairs(types) do
|
||||||
if type.records then
|
if type.records then
|
||||||
testRecordStore(type, key)
|
testRecordStore(type, key)
|
||||||
|
@ -140,9 +141,9 @@ local function testRecordStores()
|
||||||
testRecordStore(types.NPC.classes, "classes")
|
testRecordStore(types.NPC.classes, "classes")
|
||||||
testRecordStore(types.NPC.races, "races")
|
testRecordStore(types.NPC.races, "races")
|
||||||
testRecordStore(types.Player.birthSigns, "birthSigns")
|
testRecordStore(types.Player.birthSigns, "birthSigns")
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testRecordCreation()
|
testing.registerGlobalTest('record creation', function()
|
||||||
local newLight = {
|
local newLight = {
|
||||||
isCarriable = true,
|
isCarriable = true,
|
||||||
isDynamic = true,
|
isDynamic = true,
|
||||||
|
@ -165,9 +166,9 @@ local function testRecordCreation()
|
||||||
for key, value in pairs(newLight) do
|
for key, value in pairs(newLight) do
|
||||||
testing.expectEqual(record[key], value)
|
testing.expectEqual(record[key], value)
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testUTF8Chars()
|
testing.registerGlobalTest('UTF-8 characters', function()
|
||||||
testing.expectEqual(utf8.codepoint("😀"), 0x1F600)
|
testing.expectEqual(utf8.codepoint("😀"), 0x1F600)
|
||||||
|
|
||||||
local chars = {}
|
local chars = {}
|
||||||
|
@ -192,9 +193,9 @@ local function testUTF8Chars()
|
||||||
testing.expectEqual(utf8.codepoint(char), codepoint)
|
testing.expectEqual(utf8.codepoint(char), codepoint)
|
||||||
testing.expectEqual(utf8.len(char), 1)
|
testing.expectEqual(utf8.len(char), 1)
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testUTF8Strings()
|
testing.registerGlobalTest('UTF-8 strings', function()
|
||||||
local utf8str = "Hello, 你好, 🌎!"
|
local utf8str = "Hello, 你好, 🌎!"
|
||||||
|
|
||||||
local str = ""
|
local str = ""
|
||||||
|
@ -205,9 +206,9 @@ local function testUTF8Strings()
|
||||||
|
|
||||||
testing.expectEqual(utf8.len(utf8str), 13)
|
testing.expectEqual(utf8.len(utf8str), 13)
|
||||||
testing.expectEqual(utf8.offset(utf8str, 9), 11)
|
testing.expectEqual(utf8.offset(utf8str, 9), 11)
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testMemoryLimit()
|
testing.registerGlobalTest('memory limit', function()
|
||||||
local ok, err = pcall(function()
|
local ok, err = pcall(function()
|
||||||
local t = {}
|
local t = {}
|
||||||
local n = 1
|
local n = 1
|
||||||
|
@ -218,14 +219,16 @@ local function testMemoryLimit()
|
||||||
end)
|
end)
|
||||||
testing.expectEqual(ok, false, 'Script reaching memory limit should fail')
|
testing.expectEqual(ok, false, 'Script reaching memory limit should fail')
|
||||||
testing.expectEqual(err, 'not enough memory')
|
testing.expectEqual(err, 'not enough memory')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function initPlayer()
|
local function initPlayer()
|
||||||
|
local player = world.players[1]
|
||||||
player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity)
|
player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity)
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
|
return player
|
||||||
end
|
end
|
||||||
|
|
||||||
local function testVFS()
|
testing.registerGlobalTest('vfs', function()
|
||||||
local file = 'test_vfs_dir/lines.txt'
|
local file = 'test_vfs_dir/lines.txt'
|
||||||
local nosuchfile = 'test_vfs_dir/nosuchfile'
|
local nosuchfile = 'test_vfs_dir/nosuchfile'
|
||||||
testing.expectEqual(vfs.fileExists(file), true, 'lines.txt should exist')
|
testing.expectEqual(vfs.fileExists(file), true, 'lines.txt should exist')
|
||||||
|
@ -269,12 +272,11 @@ local function testVFS()
|
||||||
for _,v in pairs(expectedLines) do
|
for _,v in pairs(expectedLines) do
|
||||||
testing.expectEqual(getLine(), v)
|
testing.expectEqual(getLine(), v)
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testCommitCrime()
|
testing.registerGlobalTest('commit crime', function()
|
||||||
initPlayer()
|
local player = initPlayer()
|
||||||
local player = world.players[1]
|
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `commit crime`')
|
||||||
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `testCommitCrime`')
|
|
||||||
testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts')
|
testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts')
|
||||||
|
|
||||||
-- Reset crime level to have a clean slate
|
-- Reset crime level to have a clean slate
|
||||||
|
@ -292,82 +294,59 @@ local function testCommitCrime()
|
||||||
types.Player.setCrimeLevel(player, 0)
|
types.Player.setCrimeLevel(player, 0)
|
||||||
testing.expectEqual(I.Crimes.commitCrime(player, { victim = victim, type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, true, "Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in")
|
testing.expectEqual(I.Crimes.commitCrime(player, { victim = victim, type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, true, "Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in")
|
||||||
testing.expectEqual(types.Player.getCrimeLevel(player), 0, "Crime level should not change if the victim's alarm value is low and there's no other witnesses")
|
testing.expectEqual(types.Player.getCrimeLevel(player), 0, "Crime level should not change if the victim's alarm value is low and there's no other witnesses")
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function testRecordModelProperty()
|
testing.registerGlobalTest('record model property', function()
|
||||||
initPlayer()
|
|
||||||
local player = world.players[1]
|
local player = world.players[1]
|
||||||
testing.expectEqual(types.NPC.record(player).model, 'meshes/basicplayer.dae')
|
testing.expectEqual(types.NPC.record(player).model, 'meshes/basicplayer.dae')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function registerPlayerTest(name)
|
||||||
|
testing.registerGlobalTest(name, function()
|
||||||
|
local player = initPlayer()
|
||||||
|
testing.runLocalTest(player, name)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
tests = {
|
registerPlayerTest('player yaw rotation')
|
||||||
{'timers', testTimers},
|
registerPlayerTest('player pitch rotation')
|
||||||
{'rotating player with controls.yawChange should change rotation', function()
|
registerPlayerTest('player pitch and yaw rotation')
|
||||||
initPlayer()
|
registerPlayerTest('player rotation')
|
||||||
testing.runLocalTest(player, 'playerYawRotation')
|
registerPlayerTest('player forward running')
|
||||||
end},
|
registerPlayerTest('player diagonal walking')
|
||||||
{'rotating player with controls.pitchChange should change rotation', function()
|
registerPlayerTest('findPath')
|
||||||
initPlayer()
|
registerPlayerTest('findRandomPointAroundCircle')
|
||||||
testing.runLocalTest(player, 'playerPitchRotation')
|
registerPlayerTest('castNavigationRay')
|
||||||
end},
|
registerPlayerTest('findNearestNavMeshPosition')
|
||||||
{'rotating player with controls.pitchChange and controls.yawChange should change rotation', function()
|
registerPlayerTest('player memory limit')
|
||||||
initPlayer()
|
|
||||||
testing.runLocalTest(player, 'playerPitchAndYawRotation')
|
testing.registerGlobalTest('player weapon attack', function()
|
||||||
end},
|
local player = initPlayer()
|
||||||
{'rotating player should not lead to nan rotation', function()
|
world.createObject('basic_dagger1h', 1):moveInto(player)
|
||||||
initPlayer()
|
testing.runLocalTest(player, 'player weapon attack')
|
||||||
testing.runLocalTest(player, 'playerRotation')
|
end)
|
||||||
end},
|
|
||||||
{'playerForwardRunning', function()
|
testing.registerGlobalTest('load while teleporting - init player', function()
|
||||||
initPlayer()
|
local player = world.players[1]
|
||||||
testing.runLocalTest(player, 'playerForwardRunning')
|
player:teleport('Museum of Wonders', util.vector3(0, -1500, 111), util.transform.rotateZ(math.rad(180)))
|
||||||
end},
|
end)
|
||||||
{'playerDiagonalWalking', function()
|
|
||||||
initPlayer()
|
testing.registerGlobalTest('load while teleporting - teleport', function()
|
||||||
testing.runLocalTest(player, 'playerDiagonalWalking')
|
local player = world.players[1]
|
||||||
end},
|
local landracer = world.createObject('landracer')
|
||||||
{'findPath', function()
|
landracer:teleport(player.cell, player.position + util.vector3(0, 500, 0))
|
||||||
initPlayer()
|
coroutine.yield()
|
||||||
testing.runLocalTest(player, 'findPath')
|
|
||||||
end},
|
local door = world.getObjectByFormId(core.getFormId('the_hub.omwaddon', 26))
|
||||||
{'findRandomPointAroundCircle', function()
|
door:activateBy(player)
|
||||||
initPlayer()
|
coroutine.yield()
|
||||||
testing.runLocalTest(player, 'findRandomPointAroundCircle')
|
|
||||||
end},
|
landracer:teleport(player.cell, player.position)
|
||||||
{'castNavigationRay', function()
|
end)
|
||||||
initPlayer()
|
|
||||||
testing.runLocalTest(player, 'castNavigationRay')
|
|
||||||
end},
|
|
||||||
{'findNearestNavMeshPosition', function()
|
|
||||||
initPlayer()
|
|
||||||
testing.runLocalTest(player, 'findNearestNavMeshPosition')
|
|
||||||
end},
|
|
||||||
{'teleport', testTeleport},
|
|
||||||
{'getGMST', testGetGMST},
|
|
||||||
{'recordStores', testRecordStores},
|
|
||||||
{'recordCreation', testRecordCreation},
|
|
||||||
{'utf8Chars', testUTF8Chars},
|
|
||||||
{'utf8Strings', testUTF8Strings},
|
|
||||||
{'mwscript', testMWScript},
|
|
||||||
{'testMemoryLimit', testMemoryLimit},
|
|
||||||
{'playerMemoryLimit', function()
|
|
||||||
initPlayer()
|
|
||||||
testing.runLocalTest(player, 'playerMemoryLimit')
|
|
||||||
end},
|
|
||||||
{'player with equipped weapon on attack should damage health of other actors', function()
|
|
||||||
initPlayer()
|
|
||||||
world.createObject('basic_dagger1h', 1):moveInto(player)
|
|
||||||
testing.runLocalTest(player, 'playerWeaponAttack')
|
|
||||||
end},
|
|
||||||
{'vfs', testVFS},
|
|
||||||
{'testCommitCrime', testCommitCrime},
|
|
||||||
{'recordModelProperty', testRecordModelProperty},
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
engineHandlers = {
|
engineHandlers = {
|
||||||
onUpdate = testing.testRunner(tests),
|
onUpdate = testing.updateGlobal,
|
||||||
onPlayerAdded = function(p) player = p end,
|
|
||||||
},
|
},
|
||||||
eventHandlers = testing.eventHandlers,
|
eventHandlers = testing.globalEventHandlers,
|
||||||
}
|
}
|
94
scripts/data/integration_tests/test_lua_api/menu.lua
Normal file
94
scripts/data/integration_tests/test_lua_api/menu.lua
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
local testing = require('testing_util')
|
||||||
|
local matchers = require('matchers')
|
||||||
|
local menu = require('openmw.menu')
|
||||||
|
|
||||||
|
testing.registerMenuTest('save and load', function()
|
||||||
|
menu.newGame()
|
||||||
|
coroutine.yield()
|
||||||
|
menu.saveGame('save and load')
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
local directorySaves = {}
|
||||||
|
directorySaves['save_and_load.omwsave'] = {
|
||||||
|
playerName = '',
|
||||||
|
playerLevel = 1,
|
||||||
|
timePlayed = 0,
|
||||||
|
description = 'save and load',
|
||||||
|
contentFiles = {
|
||||||
|
'builtin.omwscripts',
|
||||||
|
'template.omwgame',
|
||||||
|
'landracer.omwaddon',
|
||||||
|
'the_hub.omwaddon',
|
||||||
|
'test_lua_api.omwscripts',
|
||||||
|
},
|
||||||
|
creationTime = matchers.isAny(),
|
||||||
|
}
|
||||||
|
local expectedAllSaves = {}
|
||||||
|
expectedAllSaves[' - 1'] = directorySaves
|
||||||
|
|
||||||
|
testing.expectThat(menu.getAllSaves(), matchers.equalTo(expectedAllSaves))
|
||||||
|
|
||||||
|
menu.loadGame(' - 1', 'save_and_load.omwsave')
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
menu.deleteGame(' - 1', 'save_and_load.omwsave')
|
||||||
|
testing.expectThat(menu.getAllSaves(), matchers.equalTo({}))
|
||||||
|
end)
|
||||||
|
|
||||||
|
testing.registerMenuTest('load while teleporting', function()
|
||||||
|
menu.newGame()
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
testing.runGlobalTest('load while teleporting - init player')
|
||||||
|
|
||||||
|
menu.saveGame('load while teleporting')
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
testing.runGlobalTest('load while teleporting - teleport')
|
||||||
|
|
||||||
|
menu.loadGame(' - 1', 'load_while_teleporting.omwsave')
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
menu.deleteGame(' - 1', 'load_while_teleporting.omwsave')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function registerGlobalTest(name, description)
|
||||||
|
testing.registerMenuTest(description or name, function()
|
||||||
|
menu.newGame()
|
||||||
|
coroutine.yield()
|
||||||
|
testing.runGlobalTest(name)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
registerGlobalTest('timers')
|
||||||
|
registerGlobalTest('teleport')
|
||||||
|
registerGlobalTest('getGMST')
|
||||||
|
registerGlobalTest('MWScript')
|
||||||
|
registerGlobalTest('record stores')
|
||||||
|
registerGlobalTest('record creation')
|
||||||
|
registerGlobalTest('UTF-8 characters')
|
||||||
|
registerGlobalTest('UTF-8 strings')
|
||||||
|
registerGlobalTest('memory limit')
|
||||||
|
registerGlobalTest('vfs')
|
||||||
|
registerGlobalTest('commit crime')
|
||||||
|
registerGlobalTest('record model property')
|
||||||
|
|
||||||
|
registerGlobalTest('player yaw rotation', 'rotating player with controls.yawChange should change rotation')
|
||||||
|
registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation')
|
||||||
|
registerGlobalTest('player pitch and yaw rotation', 'rotating player with controls.pitchChange and controls.yawChange should change rotation')
|
||||||
|
registerGlobalTest('player rotation', 'rotating player should not lead to nan rotation')
|
||||||
|
registerGlobalTest('player forward running')
|
||||||
|
registerGlobalTest('player diagonal walking')
|
||||||
|
registerGlobalTest('findPath')
|
||||||
|
registerGlobalTest('findRandomPointAroundCircle')
|
||||||
|
registerGlobalTest('castNavigationRay')
|
||||||
|
registerGlobalTest('findNearestNavMeshPosition')
|
||||||
|
registerGlobalTest('player memory limit')
|
||||||
|
registerGlobalTest('player weapon attack', 'player with equipped weapon on attack should damage health of other actors')
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineHandlers = {
|
||||||
|
onFrame = testing.makeUpdateMenu(),
|
||||||
|
},
|
||||||
|
eventHandlers = testing.menuEventHandlers,
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
content=test.omwscripts
|
content=test_lua_api.omwscripts
|
||||||
|
|
||||||
# Needed to test `core.getGMST`
|
# Needed to test `core.getGMST`
|
||||||
fallback=Water_RippleFrameCount,4
|
fallback=Water_RippleFrameCount,4
|
||||||
|
|
|
@ -6,6 +6,7 @@ local input = require('openmw.input')
|
||||||
local types = require('openmw.types')
|
local types = require('openmw.types')
|
||||||
local nearby = require('openmw.nearby')
|
local nearby = require('openmw.nearby')
|
||||||
local camera = require('openmw.camera')
|
local camera = require('openmw.camera')
|
||||||
|
local matchers = require('matchers')
|
||||||
|
|
||||||
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Controls, false)
|
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Controls, false)
|
||||||
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false)
|
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false)
|
||||||
|
@ -40,7 +41,7 @@ local function rotateByPitch(object, target)
|
||||||
rotate(object, target, nil)
|
rotate(object, target, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
testing.registerLocalTest('playerYawRotation',
|
testing.registerLocalTest('player yaw rotation',
|
||||||
function()
|
function()
|
||||||
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
||||||
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
||||||
|
@ -60,7 +61,7 @@ testing.registerLocalTest('playerYawRotation',
|
||||||
testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change')
|
testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerPitchRotation',
|
testing.registerLocalTest('player pitch rotation',
|
||||||
function()
|
function()
|
||||||
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
||||||
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
||||||
|
@ -80,7 +81,7 @@ testing.registerLocalTest('playerPitchRotation',
|
||||||
testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention')
|
testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerPitchAndYawRotation',
|
testing.registerLocalTest('player pitch and yaw rotation',
|
||||||
function()
|
function()
|
||||||
local targetPitch = math.rad(-30)
|
local targetPitch = math.rad(-30)
|
||||||
local targetYaw = math.rad(-60)
|
local targetYaw = math.rad(-60)
|
||||||
|
@ -99,7 +100,7 @@ testing.registerLocalTest('playerPitchAndYawRotation',
|
||||||
testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention')
|
testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerRotation',
|
testing.registerLocalTest('player rotation',
|
||||||
function()
|
function()
|
||||||
local rotation = math.sqrt(2)
|
local rotation = math.sqrt(2)
|
||||||
local endTime = core.getSimulationTime() + 3
|
local endTime = core.getSimulationTime() + 3
|
||||||
|
@ -113,17 +114,17 @@ testing.registerLocalTest('playerRotation',
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
|
|
||||||
local alpha1, gamma1 = self.rotation:getAnglesXZ()
|
local alpha1, gamma1 = self.rotation:getAnglesXZ()
|
||||||
testing.expectThat(alpha1, testing.isNotNan(), 'Alpha rotation in XZ convention is nan')
|
testing.expectThat(alpha1, matchers.isNotNan(), 'Alpha rotation in XZ convention is nan')
|
||||||
testing.expectThat(gamma1, testing.isNotNan(), 'Gamma rotation in XZ convention is nan')
|
testing.expectThat(gamma1, matchers.isNotNan(), 'Gamma rotation in XZ convention is nan')
|
||||||
|
|
||||||
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
|
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
|
||||||
testing.expectThat(alpha2, testing.isNotNan(), 'Alpha rotation in ZYX convention is nan')
|
testing.expectThat(alpha2, matchers.isNotNan(), 'Alpha rotation in ZYX convention is nan')
|
||||||
testing.expectThat(beta2, testing.isNotNan(), 'Beta rotation in ZYX convention is nan')
|
testing.expectThat(beta2, matchers.isNotNan(), 'Beta rotation in ZYX convention is nan')
|
||||||
testing.expectThat(gamma2, testing.isNotNan(), 'Gamma rotation in ZYX convention is nan')
|
testing.expectThat(gamma2, matchers.isNotNan(), 'Gamma rotation in ZYX convention is nan')
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerForwardRunning',
|
testing.registerLocalTest('player forward running',
|
||||||
function()
|
function()
|
||||||
local startPos = self.position
|
local startPos = self.position
|
||||||
local endTime = core.getSimulationTime() + 1
|
local endTime = core.getSimulationTime() + 1
|
||||||
|
@ -141,7 +142,7 @@ testing.registerLocalTest('playerForwardRunning',
|
||||||
testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord')
|
testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerDiagonalWalking',
|
testing.registerLocalTest('player diagonal walking',
|
||||||
function()
|
function()
|
||||||
local startPos = self.position
|
local startPos = self.position
|
||||||
local endTime = core.getSimulationTime() + 1
|
local endTime = core.getSimulationTime() + 1
|
||||||
|
@ -220,7 +221,7 @@ testing.registerLocalTest('findNearestNavMeshPosition',
|
||||||
'Navigation mesh position ' .. testing.formatActualExpected(result, expected))
|
'Navigation mesh position ' .. testing.formatActualExpected(result, expected))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerMemoryLimit',
|
testing.registerLocalTest('player memory limit',
|
||||||
function()
|
function()
|
||||||
local ok, err = pcall(function()
|
local ok, err = pcall(function()
|
||||||
local str = 'a'
|
local str = 'a'
|
||||||
|
@ -232,7 +233,7 @@ testing.registerLocalTest('playerMemoryLimit',
|
||||||
testing.expectEqual(err, 'not enough memory')
|
testing.expectEqual(err, 'not enough memory')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('playerWeaponAttack',
|
testing.registerLocalTest('player weapon attack',
|
||||||
function()
|
function()
|
||||||
camera.setMode(camera.MODE.ThirdPerson)
|
camera.setMode(camera.MODE.ThirdPerson)
|
||||||
|
|
||||||
|
@ -346,5 +347,5 @@ return {
|
||||||
engineHandlers = {
|
engineHandlers = {
|
||||||
onFrame = testing.updateLocal,
|
onFrame = testing.updateLocal,
|
||||||
},
|
},
|
||||||
eventHandlers = testing.eventHandlers
|
eventHandlers = testing.localEventHandlers,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
GLOBAL: test.lua
|
|
||||||
PLAYER: player.lua
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
MENU: menu.lua
|
||||||
|
GLOBAL: global.lua
|
||||||
|
PLAYER: player.lua
|
218
scripts/data/integration_tests/testing_util/matchers.lua
Normal file
218
scripts/data/integration_tests/testing_util/matchers.lua
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
local module = {}
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Matcher verifying that distance between given value and expected is not greater than maxDistance.
|
||||||
|
-- @function elementsAreArray
|
||||||
|
-- @param expected#vector.
|
||||||
|
-- @usage
|
||||||
|
-- expectThat(util.vector2(0, 0), closeToVector(util.vector2(0, 1), 1))
|
||||||
|
function module.closeToVector(expected, maxDistance)
|
||||||
|
return function(actual)
|
||||||
|
local distance = (expected - actual):length()
|
||||||
|
if distance <= maxDistance then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Matcher verifying that given value is an array each element of which matches elements of expected.
|
||||||
|
-- @function elementsAreArray
|
||||||
|
-- @param expected#array of values or matcher functions.
|
||||||
|
-- @usage
|
||||||
|
-- local t = {42, 13}
|
||||||
|
-- local matcher = function(actual)
|
||||||
|
-- if actual ~= 42 then
|
||||||
|
-- return string.format('%s is not 42', actual)
|
||||||
|
-- end
|
||||||
|
-- return ''
|
||||||
|
-- end
|
||||||
|
-- expectThat({42, 13}, elementsAreArray({matcher, 13}))
|
||||||
|
function module.elementsAreArray(expected)
|
||||||
|
local expected_matchers = {}
|
||||||
|
for i, v in ipairs(expected) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
expected_matchers[i] = v
|
||||||
|
else
|
||||||
|
expected_matchers[i] = function (other)
|
||||||
|
if expected[i].__eq(expected[i], other) then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
return string.format('%s element %s does no match expected: %s', i, other, expected[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return function(actual)
|
||||||
|
if #actual < #expected_matchers then
|
||||||
|
return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers)
|
||||||
|
end
|
||||||
|
local message = ''
|
||||||
|
for i, v in ipairs(actual) do
|
||||||
|
if i > #expected_matchers then
|
||||||
|
message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local match_message = expected_matchers[i](v)
|
||||||
|
if match_message ~= '' then
|
||||||
|
message = string.format('%s\n%s', message, match_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Matcher verifying that given number is not a nan.
|
||||||
|
-- @function isNotNan
|
||||||
|
-- @usage
|
||||||
|
-- expectThat(value, isNotNan())
|
||||||
|
function module.isNotNan()
|
||||||
|
return function(actual)
|
||||||
|
if actual ~= actual then
|
||||||
|
return 'actual value is nan, expected to be not nan'
|
||||||
|
end
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Matcher accepting any value.
|
||||||
|
-- @function isAny
|
||||||
|
-- @usage
|
||||||
|
-- expectThat(value, isAny())
|
||||||
|
function module.isAny()
|
||||||
|
return function(actual)
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function serializeArray(a)
|
||||||
|
local result = nil
|
||||||
|
for _, v in ipairs(a) do
|
||||||
|
if result == nil then
|
||||||
|
result = string.format('{%s', serialize(v))
|
||||||
|
else
|
||||||
|
result = string.format('%s, %s', result, serialize(v))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if result == nil then
|
||||||
|
return '{}'
|
||||||
|
end
|
||||||
|
return string.format('%s}', result)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function serializeTable(t)
|
||||||
|
local result = nil
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if result == nil then
|
||||||
|
result = string.format('{%q = %s', k, serialize(v))
|
||||||
|
else
|
||||||
|
result = string.format('%s, %q = %s', result, k, serialize(v))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if result == nil then
|
||||||
|
return '{}'
|
||||||
|
end
|
||||||
|
return string.format('%s}', result)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isArray(t)
|
||||||
|
local i = 1
|
||||||
|
for _ in pairs(t) do
|
||||||
|
if t[i] == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function serialize(v)
|
||||||
|
local t = type(v)
|
||||||
|
if t == 'string' then
|
||||||
|
return string.format('%q', v)
|
||||||
|
elseif t == 'table' then
|
||||||
|
if isArray(v) then
|
||||||
|
return serializeArray(v)
|
||||||
|
end
|
||||||
|
return serializeTable(v)
|
||||||
|
end
|
||||||
|
return string.format('%s', v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compareScalars(v1, v2)
|
||||||
|
if v1 == v2 then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
if type(v1) == 'string' then
|
||||||
|
return string.format('%q ~= %q', v1, v2)
|
||||||
|
end
|
||||||
|
return string.format('%s ~= %s', v1, v2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function collectKeys(t)
|
||||||
|
local result = {}
|
||||||
|
for key in pairs(t) do
|
||||||
|
table.insert(result, key)
|
||||||
|
end
|
||||||
|
table.sort(result)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compareTables(t1, t2)
|
||||||
|
local keys1 = collectKeys(t1)
|
||||||
|
local keys2 = collectKeys(t2)
|
||||||
|
if #keys1 ~= #keys2 then
|
||||||
|
return string.format('table size mismatch: %d ~= %d', #keys1, #keys2)
|
||||||
|
end
|
||||||
|
for i = 1, #keys1 do
|
||||||
|
local key1 = keys1[i]
|
||||||
|
local key2 = keys2[i]
|
||||||
|
if key1 ~= key2 then
|
||||||
|
return string.format('table keys mismatch: %q ~= %q', key1, key2)
|
||||||
|
end
|
||||||
|
local d = compare(t1[key1], t2[key2])
|
||||||
|
if d ~= '' then
|
||||||
|
return string.format('table values mismatch at key %s: %s', serialize(key1), d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
function compare(v1, v2)
|
||||||
|
local type1 = type(v1)
|
||||||
|
local type2 = type(v2)
|
||||||
|
if type2 == 'function' then
|
||||||
|
return v2(v1)
|
||||||
|
end
|
||||||
|
if type1 ~= type2 then
|
||||||
|
return string.format('types mismatch: %s ~= %s', type1, type2)
|
||||||
|
end
|
||||||
|
if type1 == 'nil' then
|
||||||
|
return ''
|
||||||
|
elseif type1 == 'table' then
|
||||||
|
return compareTables(v1, v2)
|
||||||
|
elseif type1 == 'nil' or type1 == 'boolean' or type1 == 'number' or type1 == 'string' then
|
||||||
|
return compareScalars(v1, v2)
|
||||||
|
end
|
||||||
|
error('unsupported type: %s', type1)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Matcher verifying that given value is equal to expected. Accepts nil, boolean, number, string and table or matcher
|
||||||
|
-- function.
|
||||||
|
-- @function equalTo
|
||||||
|
-- @usage
|
||||||
|
-- expectThat({a = {42, 'foo', {b = true}}}, equalTo({a = {42, 'foo', {b = true}}}))
|
||||||
|
function module.equalTo(expected)
|
||||||
|
return function(actual)
|
||||||
|
local diff = compare(actual, expected)
|
||||||
|
if diff == '' then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
return string.format('%s; actual: %s; expected: %s', diff, serialize(actual, ''), serialize(expected, ''))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return module
|
|
@ -2,23 +2,22 @@ local core = require('openmw.core')
|
||||||
local util = require('openmw.util')
|
local util = require('openmw.util')
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
local menuTestsOrder = {}
|
||||||
|
local menuTests = {}
|
||||||
|
|
||||||
|
local globalTestsOrder = {}
|
||||||
|
local globalTests = {}
|
||||||
|
local globalTestRunner = nil
|
||||||
|
local currentGlobalTest = nil
|
||||||
|
local currentGlobalTestError = nil
|
||||||
|
|
||||||
|
local localTests = {}
|
||||||
|
local localTestRunner = nil
|
||||||
local currentLocalTest = nil
|
local currentLocalTest = nil
|
||||||
local currentLocalTestError = nil
|
local currentLocalTestError = nil
|
||||||
|
|
||||||
function M.testRunner(tests)
|
local function makeTestCoroutine(fn)
|
||||||
local fn = function()
|
|
||||||
for i, test in ipairs(tests) do
|
|
||||||
local name, fn = unpack(test)
|
|
||||||
print('TEST_START', i, name)
|
|
||||||
local status, err = pcall(fn)
|
|
||||||
if status then
|
|
||||||
print('TEST_OK', i, name)
|
|
||||||
else
|
|
||||||
print('TEST_FAILED', i, name, err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.quit()
|
|
||||||
end
|
|
||||||
local co = coroutine.create(fn)
|
local co = coroutine.create(fn)
|
||||||
return function()
|
return function()
|
||||||
if coroutine.status(co) ~= 'dead' then
|
if coroutine.status(co) ~= 'dead' then
|
||||||
|
@ -27,6 +26,64 @@ function M.testRunner(tests)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function runTests(tests)
|
||||||
|
for i, test in ipairs(tests) do
|
||||||
|
local name, fn = unpack(test)
|
||||||
|
print('TEST_START', i, name)
|
||||||
|
local status, err = pcall(fn)
|
||||||
|
if status then
|
||||||
|
print('TEST_OK', i, name)
|
||||||
|
else
|
||||||
|
print('TEST_FAILED', i, name, err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core.quit()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.makeUpdateMenu()
|
||||||
|
return makeTestCoroutine(function()
|
||||||
|
print('Running menu tests...')
|
||||||
|
runTests(menuTestsOrder)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.makeUpdateGlobal()
|
||||||
|
return makeTestCoroutine(function()
|
||||||
|
print('Running global tests...')
|
||||||
|
runTests(globalTestsOrder)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.registerMenuTest(name, fn)
|
||||||
|
menuTests[name] = fn
|
||||||
|
table.insert(menuTestsOrder, {name, fn})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.runGlobalTest(name)
|
||||||
|
currentGlobalTest = name
|
||||||
|
currentGlobalTestError = nil
|
||||||
|
core.sendGlobalEvent('runGlobalTest', name)
|
||||||
|
while currentGlobalTest do
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
if currentGlobalTestError then
|
||||||
|
error(currentGlobalTestError, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.registerGlobalTest(name, fn)
|
||||||
|
globalTests[name] = fn
|
||||||
|
table.insert(globalTestsOrder, {name, fn})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.updateGlobal()
|
||||||
|
if globalTestRunner and coroutine.status(globalTestRunner) ~= 'dead' then
|
||||||
|
coroutine.resume(globalTestRunner)
|
||||||
|
else
|
||||||
|
globalTestRunner = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function M.runLocalTest(obj, name)
|
function M.runLocalTest(obj, name)
|
||||||
currentLocalTest = name
|
currentLocalTest = name
|
||||||
currentLocalTestError = nil
|
currentLocalTestError = nil
|
||||||
|
@ -39,7 +96,21 @@ function M.runLocalTest(obj, name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.expect(cond, delta, msg)
|
function M.registerLocalTest(name, fn)
|
||||||
|
localTests[name] = fn
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.updateLocal()
|
||||||
|
if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then
|
||||||
|
if not core.isWorldPaused() then
|
||||||
|
coroutine.resume(localTestRunner)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
localTestRunner = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.expect(cond, msg)
|
||||||
if not cond then
|
if not cond then
|
||||||
error(msg or '"true" expected', 2)
|
error(msg or '"true" expected', 2)
|
||||||
end
|
end
|
||||||
|
@ -87,76 +158,6 @@ function M.expectNotEqual(v1, v2, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.closeToVector(expected, maxDistance)
|
|
||||||
return function(actual)
|
|
||||||
local distance = (expected - actual):length()
|
|
||||||
if distance <= maxDistance then
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---
|
|
||||||
-- Matcher verifying that given value is an array each element of which matches elements of expected.
|
|
||||||
-- @function elementsAreArray
|
|
||||||
-- @param expected#array of values or matcher functions.
|
|
||||||
-- @usage
|
|
||||||
-- local t = {42, 13}
|
|
||||||
-- local matcher = function(actual)
|
|
||||||
-- if actual ~= 42 then
|
|
||||||
-- return string.format('%s is not 42', actual)
|
|
||||||
-- end
|
|
||||||
-- return ''
|
|
||||||
-- end
|
|
||||||
-- expectThat({42, 13}, elementsAreArray({matcher, 13}))
|
|
||||||
function M.elementsAreArray(expected)
|
|
||||||
local expected_matchers = {}
|
|
||||||
for i, v in ipairs(expected) do
|
|
||||||
if type(v) == 'function' then
|
|
||||||
expected_matchers[i] = v
|
|
||||||
else
|
|
||||||
expected_matchers[i] = function (other)
|
|
||||||
if expected[i].__eq(expected[i], other) then
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
return string.format('%s element %s does no match expected: %s', i, other, expected[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return function(actual)
|
|
||||||
if #actual < #expected_matchers then
|
|
||||||
return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers)
|
|
||||||
end
|
|
||||||
local message = ''
|
|
||||||
for i, v in ipairs(actual) do
|
|
||||||
if i > #expected_matchers then
|
|
||||||
message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
local match_message = expected_matchers[i](v)
|
|
||||||
if match_message ~= '' then
|
|
||||||
message = string.format('%s\n%s', message, match_message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---
|
|
||||||
-- Matcher verifying that given number is not a nan.
|
|
||||||
-- @function isNotNan
|
|
||||||
-- @usage
|
|
||||||
-- expectThat(value, isNotNan())
|
|
||||||
function M.isNotNan(expected)
|
|
||||||
return function(actual)
|
|
||||||
if actual ~= actual then
|
|
||||||
return 'actual value is nan, expected to be not nan'
|
|
||||||
end
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Verifies that given value matches provided matcher.
|
-- Verifies that given value matches provided matcher.
|
||||||
-- @function expectThat
|
-- @function expectThat
|
||||||
|
@ -182,28 +183,50 @@ function M.formatActualExpected(actual, expected)
|
||||||
return string.format('actual: %s, expected: %s', actual, expected)
|
return string.format('actual: %s, expected: %s', actual, expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
local localTests = {}
|
-- used only in menu scripts
|
||||||
local localTestRunner = nil
|
M.menuEventHandlers = {
|
||||||
|
globalTestFinished = function(data)
|
||||||
function M.registerLocalTest(name, fn)
|
if data.name ~= currentGlobalTest then
|
||||||
localTests[name] = fn
|
error(string.format('globalTestFinished with incorrect name %s, expected %s', data.name, currentGlobalTest), 2)
|
||||||
end
|
|
||||||
|
|
||||||
function M.updateLocal()
|
|
||||||
if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then
|
|
||||||
if not core.isWorldPaused() then
|
|
||||||
coroutine.resume(localTestRunner)
|
|
||||||
end
|
end
|
||||||
else
|
currentGlobalTest = nil
|
||||||
localTestRunner = nil
|
currentGlobalTestError = data.errMsg
|
||||||
end
|
end,
|
||||||
end
|
}
|
||||||
|
|
||||||
M.eventHandlers = {
|
-- used only in global scripts
|
||||||
runLocalTest = function(name) -- used only in local scripts
|
M.globalEventHandlers = {
|
||||||
|
runGlobalTest = function(name)
|
||||||
|
fn = globalTests[name]
|
||||||
|
local types = require('openmw.types')
|
||||||
|
local world = require('openmw.world')
|
||||||
|
if not fn then
|
||||||
|
types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg='Global test is not found'})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
globalTestRunner = coroutine.create(function()
|
||||||
|
local status, err = pcall(fn)
|
||||||
|
if status then
|
||||||
|
err = nil
|
||||||
|
end
|
||||||
|
types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg=err})
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
localTestFinished = function(data)
|
||||||
|
if data.name ~= currentLocalTest then
|
||||||
|
error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest), 2)
|
||||||
|
end
|
||||||
|
currentLocalTest = nil
|
||||||
|
currentLocalTestError = data.errMsg
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- used only in local scripts
|
||||||
|
M.localEventHandlers = {
|
||||||
|
runLocalTest = function(name)
|
||||||
fn = localTests[name]
|
fn = localTests[name]
|
||||||
if not fn then
|
if not fn then
|
||||||
core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Test not found'})
|
core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Local test is not found'})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
localTestRunner = coroutine.create(function()
|
localTestRunner = coroutine.create(function()
|
||||||
|
@ -214,13 +237,6 @@ M.eventHandlers = {
|
||||||
core.sendGlobalEvent('localTestFinished', {name=name, errMsg=err})
|
core.sendGlobalEvent('localTestFinished', {name=name, errMsg=err})
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
localTestFinished = function(data) -- used only in global scripts
|
|
||||||
if data.name ~= currentLocalTest then
|
|
||||||
error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest))
|
|
||||||
end
|
|
||||||
currentLocalTest = nil
|
|
||||||
currentLocalTestError = data.errMsg
|
|
||||||
end,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue