mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 21:07:59 +03:00
Compare commits
215 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 | ||
![]() |
2b5d076ff8 | ||
![]() |
2ca1850ea9 | ||
![]() |
9a35e3f64d | ||
![]() |
fdba5d4cb7 | ||
![]() |
ced142da92 | ||
![]() |
e5ad1cd214 | ||
![]() |
9f85e51934 | ||
![]() |
569ed4559f | ||
![]() |
5354a5f786 | ||
![]() |
c691917172 | ||
![]() |
73bb17009e | ||
![]() |
51d73e37df | ||
![]() |
a49a900a7b | ||
![]() |
01ea2ad08c | ||
![]() |
8cb1838c4a | ||
![]() |
2892e19c43 | ||
![]() |
0e19b1dd75 | ||
![]() |
f800f63ee5 | ||
![]() |
5776eea1b0 | ||
![]() |
5f92d520ee | ||
![]() |
9bf6a15ff5 | ||
![]() |
c5a1ca7c3e | ||
![]() |
7a9c2d5e88 | ||
![]() |
981ca957c1 | ||
![]() |
8b62f02523 | ||
![]() |
c298210844 | ||
![]() |
f80c7b2355 | ||
![]() |
124ada8d14 | ||
![]() |
95312139d5 | ||
![]() |
8bbb46b52c | ||
![]() |
888415059c | ||
![]() |
1e0bdcc270 | ||
![]() |
97717e6fce | ||
![]() |
990096ff9b | ||
![]() |
82307d4e6b | ||
![]() |
b0e9df0139 | ||
![]() |
f0cee09b7c | ||
![]() |
6e9d15f91d | ||
![]() |
62d1cdcdac | ||
![]() |
fd358396fc | ||
![]() |
24468fd965 | ||
![]() |
c8fe596fc4 | ||
![]() |
c50d8195bb | ||
![]() |
7670afcba1 | ||
![]() |
dc3264a3a5 | ||
![]() |
1bb3198b71 | ||
![]() |
d400c0959c | ||
![]() |
3a98b945a8 | ||
![]() |
a6676fd6f3 | ||
![]() |
73bb281f34 | ||
![]() |
f891a7c3b3 | ||
![]() |
614ca25d3b | ||
![]() |
74018162c7 | ||
![]() |
3e3dfac4e0 | ||
![]() |
dad22cb672 | ||
![]() |
07cc2a72bb | ||
![]() |
835ad09657 | ||
![]() |
cd53cbbea2 | ||
![]() |
5b788baa35 | ||
![]() |
04689334c5 | ||
![]() |
d71e4ec9f0 | ||
![]() |
6ede5635b3 | ||
![]() |
602a429a68 | ||
![]() |
3b05ec0ab1 | ||
![]() |
ad8f6e5eb6 | ||
![]() |
63e3b8f41b | ||
![]() |
19793c21b4 | ||
![]() |
86d56a0b1a | ||
![]() |
782c274d86 | ||
![]() |
d2610973dd | ||
![]() |
3405dbab6d | ||
![]() |
5626d925e3 | ||
![]() |
c1960635d2 | ||
![]() |
eaf9488ba0 | ||
![]() |
cfa1ad0b33 | ||
![]() |
1aa4ef029c | ||
![]() |
fe571c1a4d | ||
![]() |
a17bffda26 | ||
![]() |
c04cd2dfa4 | ||
![]() |
81287034fc | ||
![]() |
a3531fe954 | ||
![]() |
e583e64380 | ||
![]() |
4c95e91a8d | ||
![]() |
4428c1db7d | ||
![]() |
5b1aafb77a | ||
![]() |
517aa81938 | ||
![]() |
b2c0d20d56 | ||
![]() |
efe72ea2d5 | ||
![]() |
152dfacab2 | ||
![]() |
dec9ce4a5f | ||
![]() |
a1df9afc9a | ||
![]() |
ea51c55d00 | ||
![]() |
a2f5e1c075 | ||
![]() |
6071de9d1d | ||
![]() |
2762be9f85 | ||
![]() |
9ae12baee1 | ||
![]() |
57ffc11fba | ||
![]() |
2d0f45ea41 | ||
![]() |
e90c4187ff | ||
![]() |
af9a9a6d64 | ||
![]() |
c16064e6f3 | ||
![]() |
e515e99937 | ||
![]() |
2a62dd728f | ||
![]() |
9fc62be2c6 | ||
![]() |
3ef2084f80 |
180 changed files with 2150 additions and 1261 deletions
21
.clang-tidy
21
.clang-tidy
|
@ -1,18 +1,15 @@
|
|||
Checks: >
|
||||
-*,
|
||||
boost-*,
|
||||
portability-*,
|
||||
clang-analyzer-*,
|
||||
-clang-analyzer-optin*,
|
||||
-clang-analyzer-optin.*,
|
||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
||||
-clang-analyzer-cplusplus.NewDelete,
|
||||
-clang-analyzer-core.CallAndMessage,
|
||||
-modernize-avoid-bind
|
||||
WarningsAsErrors: >
|
||||
-*,
|
||||
boost-*,
|
||||
portability-*,
|
||||
clang-analyzer-*,
|
||||
-clang-analyzer-optin*,
|
||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
||||
-clang-analyzer-core.CallAndMessage
|
||||
HeaderFilterRegex: '^(apps|components)'
|
||||
modernize-avoid-bind,
|
||||
readability-identifier-naming
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '(apps|components)/'
|
||||
CheckOptions:
|
||||
- key: readability-identifier-naming.ConceptCase
|
||||
value: CamelCase
|
||||
|
|
6
.github/workflows/windows.yml
vendored
6
.github/workflows/windows.yml
vendored
|
@ -202,7 +202,11 @@ jobs:
|
|||
AWS_DEFAULT_REGION: eu-west-3
|
||||
if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }}
|
||||
working-directory: ${{ github.workspace }}/SymStore
|
||||
run: aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym
|
||||
run: |
|
||||
choco uninstall awscli -y
|
||||
choco install awscli -y --version=2.22.35
|
||||
aws --version
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym
|
||||
|
||||
- name: Add install directory to PATH
|
||||
shell: bash
|
||||
|
|
|
@ -512,9 +512,16 @@ Ubuntu_GCC_integration_tests_asan:
|
|||
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done
|
||||
- |
|
||||
if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then
|
||||
echo "[default]" > ~/.s3cfg
|
||||
echo "access_key = ${AWS_ACCESS_KEY_ID}" >> ~/.s3cfg
|
||||
echo "secret_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.s3cfg
|
||||
echo "host_base = rgw.ctrl-c.liu.se" >> ~/.s3cfg
|
||||
echo "host_bucket = %(bucket)s.rgw.ctrl-c.liu.se" >> ~/.s3cfg
|
||||
echo "use_https = True" >> ~/.s3cfg
|
||||
|
||||
artifactDirectory="${CI_PROJECT_NAMESPACE//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_REF_NAME//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_SHORT_SHA//[\"<>|$'\t'\/\\?*]/_}-${CI_JOB_ID//[\"<>|$'\t'\/\\?*]/_}/"
|
||||
for dmg in *.dmg; do
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "${dmg}" s3://openmw-artifacts/${artifactDirectory}
|
||||
s3cmd put "${dmg}" s3://openmw-artifacts/${artifactDirectory}
|
||||
done
|
||||
fi
|
||||
- ccache -s
|
||||
|
@ -540,7 +547,7 @@ macOS14_Xcode15_arm64:
|
|||
script:
|
||||
- apt-get update
|
||||
- apt-get install -y curl gcab unzip
|
||||
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscli-exe-linux-x86_64.zip
|
||||
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.35.zip" -o awscli-exe-linux-x86_64.zip
|
||||
- unzip -d awscli-exe-linux-x86_64 awscli-exe-linux-x86_64.zip
|
||||
- pushd awscli-exe-linux-x86_64
|
||||
- ./aws/install
|
||||
|
@ -587,7 +594,7 @@ macOS14_Xcode15_arm64:
|
|||
- choco install vswhere -y
|
||||
- choco install ninja -y
|
||||
- choco install python -y
|
||||
- choco install awscli -y
|
||||
- choco install awscli -y --version=2.22.35
|
||||
- refreshenv
|
||||
- |
|
||||
function Make-SafeFileName {
|
||||
|
@ -621,15 +628,20 @@ macOS14_Xcode15_arm64:
|
|||
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
|
||||
- $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/"
|
||||
- Get-ChildItem -Recurse *.ilk | Remove-Item
|
||||
- aws --version
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
Push-Location ..
|
||||
..\CI\Store-Symbols.ps1 -SkipCompress
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
Pop-Location
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
|
@ -637,13 +649,20 @@ macOS14_Xcode15_arm64:
|
|||
- |
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
- |
|
||||
if ($executables) {
|
||||
foreach ($exe in $executables.Split(',')) {
|
||||
& .\$exe
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
}
|
||||
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
|
||||
after_script:
|
||||
- Get-Volume
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
key: ninja-2022-v11
|
||||
key: ninja-2022-v12
|
||||
paths:
|
||||
- ccache
|
||||
- deps
|
||||
|
@ -738,7 +757,7 @@ macOS14_Xcode15_arm64:
|
|||
- choco install 7zip -y
|
||||
- choco install vswhere -y
|
||||
- choco install python -y
|
||||
- choco install awscli -y
|
||||
- choco install awscli -y --version=2.22.35
|
||||
- refreshenv
|
||||
- |
|
||||
function Make-SafeFileName {
|
||||
|
@ -767,15 +786,20 @@ macOS14_Xcode15_arm64:
|
|||
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
|
||||
- $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/"
|
||||
- Get-ChildItem -Recurse *.ilk | Remove-Item
|
||||
- aws --version
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
Push-Location ..
|
||||
..\CI\Store-Symbols.ps1 -SkipCompress
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
Pop-Location
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
|
@ -783,13 +807,20 @@ macOS14_Xcode15_arm64:
|
|||
- |
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
- |
|
||||
if ($executables) {
|
||||
foreach ($exe in $executables.Split(',')) {
|
||||
& .\$exe
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
}
|
||||
}
|
||||
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
|
||||
after_script:
|
||||
- Get-Volume
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
key: msbuild-2022-v11
|
||||
key: msbuild-2022-v12
|
||||
paths:
|
||||
- deps
|
||||
- MSVC2022_64/deps/Qt
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
|
||||
Bug #6025: Subrecords cannot overlap records
|
||||
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
|
||||
Bug #6097: Level Progress Tooltip Sometimes Not Updated
|
||||
Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item
|
||||
Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly
|
||||
Bug #6190: Unintuitive sun specularity time of day dependence
|
||||
|
@ -228,6 +227,11 @@
|
|||
Bug #8252: Plugin dependencies are not required to be loaded
|
||||
Bug #8295: Post-processing chain is case-sensitive
|
||||
Bug #8299: Crash while smoothing landscape
|
||||
Bug #8364: Crash when clicking scrollbar without handle (divide by zero)
|
||||
Bug #8378: Korean bitmap fonts are unusable
|
||||
Bug #8439: Creatures without models can crash the game
|
||||
Bug #8441: Freeze when using video main menu replacers
|
||||
Bug #8462: Crashes when resizing the window on macOS
|
||||
Feature #1415: Infinite fall failsafe
|
||||
Feature #2566: Handle NAM9 records for manual cell references
|
||||
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking
|
||||
|
@ -392,6 +396,7 @@
|
|||
Bug #6066: Addtopic "return" does not work from within script. No errors thrown
|
||||
Bug #6067: ESP loader fails for certain subrecord orders
|
||||
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
|
||||
Bug #6097: Level Progress Tooltip Sometimes Not Updated
|
||||
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
|
||||
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
|
||||
Bug #6109: Crash when playing a custom made menu_background file
|
||||
|
|
|
@ -7,7 +7,7 @@ export HOMEBREW_AUTOREMOVE=1
|
|||
brew tap --repair
|
||||
brew update --quiet
|
||||
|
||||
brew install curl xquartz gd fontconfig freetype harfbuzz brotli
|
||||
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd
|
||||
|
||||
command -v ccache >/dev/null 2>&1 || brew install ccache
|
||||
command -v cmake >/dev/null 2>&1 || brew install cmake
|
||||
|
|
|
@ -38,7 +38,7 @@ fi
|
|||
|
||||
if [[ $CI_CLANG_TIDY ]]; then
|
||||
CMAKE_CONF_OPTS+=(
|
||||
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*"
|
||||
-DCMAKE_CXX_CLANG_TIDY=clang-tidy
|
||||
-DBUILD_COMPONENTS_TESTS=ON
|
||||
-DBUILD_OPENMW_TESTS=ON
|
||||
-DBUILD_OPENCS_TESTS=ON
|
||||
|
|
|
@ -126,10 +126,24 @@ export APT_CACHE_DIR="${PWD}/apt-cache"
|
|||
export DEBIAN_FRONTEND=noninteractive
|
||||
set -x
|
||||
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
|
||||
add-apt-repository -y ppa:openmw/openmw
|
||||
add-apt-repository -y ppa:openmw/openmw-daily
|
||||
add-apt-repository -y ppa:openmw/staging
|
||||
|
||||
while true; do
|
||||
add-apt-repository -y ppa:openmw/openmw && break
|
||||
done
|
||||
|
||||
while true; do
|
||||
add-apt-repository -y ppa:openmw/openmw-daily && break
|
||||
done
|
||||
|
||||
while true; do
|
||||
add-apt-repository -y ppa:openmw/staging && break
|
||||
done
|
||||
|
||||
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null
|
||||
apt list --installed
|
||||
|
|
|
@ -9,7 +9,7 @@ git checkout FETCH_HEAD
|
|||
cd ..
|
||||
|
||||
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
|
||||
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"
|
||||
|
|
|
@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
|
|||
set(OPENMW_VERSION_MAJOR 0)
|
||||
set(OPENMW_VERSION_MINOR 49)
|
||||
set(OPENMW_VERSION_RELEASE 0)
|
||||
set(OPENMW_LUA_API_REVISION 70)
|
||||
set(OPENMW_LUA_API_REVISION 72)
|
||||
set(OPENMW_POSTPROCESSING_API_REVISION 2)
|
||||
|
||||
set(OPENMW_VERSION_COMMITHASH "")
|
||||
|
@ -860,7 +860,6 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
|
|||
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
|
||||
include(BundleUtilities)
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(ABSOLUTE_PLUGINS "")
|
||||
|
|
|
@ -179,7 +179,7 @@ namespace
|
|||
generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
|
||||
std::size_t n = 0;
|
||||
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
const auto& key = keys[n++ % keys.size()];
|
||||
auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh);
|
||||
|
|
|
@ -104,7 +104,7 @@ namespace
|
|||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serialize());
|
||||
if (++i >= refIds.size())
|
||||
|
@ -118,7 +118,7 @@ namespace
|
|||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
|
@ -131,7 +131,7 @@ namespace
|
|||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serializeText());
|
||||
if (++i >= refIds.size())
|
||||
|
@ -145,7 +145,7 @@ namespace
|
|||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
|
@ -158,7 +158,7 @@ namespace
|
|||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serializeText());
|
||||
if (++i >= refIds.size())
|
||||
|
@ -172,7 +172,7 @@ namespace
|
|||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
|
@ -185,7 +185,7 @@ namespace
|
|||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateIndexRefIds(random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serializeText());
|
||||
if (++i >= refIds.size())
|
||||
|
@ -199,7 +199,7 @@ namespace
|
|||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
|
@ -212,7 +212,7 @@ namespace
|
|||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serializeText());
|
||||
if (++i >= refIds.size())
|
||||
|
@ -226,7 +226,7 @@ namespace
|
|||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace
|
|||
{
|
||||
void settingsManager(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog"));
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace
|
|||
|
||||
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::getBool("transparent postpass", "Post Processing"));
|
||||
|
@ -26,7 +26,7 @@ namespace
|
|||
|
||||
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::getBool("transparent postpass", "Post Processing"));
|
||||
|
@ -36,7 +36,7 @@ namespace
|
|||
|
||||
void localStatic(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
static float v = Settings::Manager::getFloat("sky blending start", "Fog");
|
||||
benchmark::DoNotOptimize(v);
|
||||
|
@ -45,7 +45,7 @@ namespace
|
|||
|
||||
void localStatic2(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
static float v1 = Settings::Manager::getFloat("near clip", "Camera");
|
||||
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
|
||||
|
@ -56,7 +56,7 @@ namespace
|
|||
|
||||
void localStatic3(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
static float v1 = Settings::Manager::getFloat("near clip", "Camera");
|
||||
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
|
||||
|
@ -69,7 +69,7 @@ namespace
|
|||
|
||||
void settingsStorage(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
float v = Settings::fog().mSkyBlendingStart.get();
|
||||
benchmark::DoNotOptimize(v);
|
||||
|
@ -78,7 +78,7 @@ namespace
|
|||
|
||||
void settingsStorage2(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
bool v1 = Settings::postProcessing().mTransparentPostpass.get();
|
||||
float v2 = Settings::camera().mNearClip.get();
|
||||
|
@ -89,7 +89,7 @@ namespace
|
|||
|
||||
void settingsStorage3(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
bool v1 = Settings::postProcessing().mTransparentPostpass.get();
|
||||
float v2 = Settings::camera().mNearClip.get();
|
||||
|
@ -102,7 +102,7 @@ namespace
|
|||
|
||||
void settingsStorageGet(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start"));
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ namespace
|
|||
|
||||
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<float>("Camera", "near clip"));
|
||||
|
@ -119,7 +119,7 @@ namespace
|
|||
|
||||
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<float>("Camera", "near clip"));
|
||||
|
|
|
@ -53,8 +53,6 @@ namespace
|
|||
|
||||
bpo::options_description makeOptionsDescription()
|
||||
{
|
||||
using Fallback::FallbackMap;
|
||||
|
||||
bpo::options_description result;
|
||||
auto addOption = result.add_options();
|
||||
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\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");
|
||||
|
||||
Files::ConfigurationManager::addCommonOptions(result);
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include <components/detournavigator/makenavmesh.hpp>
|
||||
#include <components/detournavigator/navmeshdbutils.hpp>
|
||||
#include <components/detournavigator/serialization.hpp>
|
||||
#include <components/files/conversion.hpp>
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
#include <components/testing/util.hpp>
|
||||
|
||||
#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
|
||||
{
|
||||
const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) };
|
||||
|
|
|
@ -88,4 +88,31 @@ namespace
|
|||
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)
|
||||
{
|
||||
const auto fileName = temporaryFilePath("fileName");
|
||||
const auto fileName = outputFilePath("fileName");
|
||||
std::string content;
|
||||
std::fill_n(std::back_inserter(content), 1, 'a');
|
||||
std::istringstream stream(content);
|
||||
|
@ -41,7 +41,7 @@ namespace
|
|||
|
||||
TEST_P(FilesGetHash, shouldReturnHashForStringStream)
|
||||
{
|
||||
const auto fileName = temporaryFilePath("fileName");
|
||||
const auto fileName = outputFilePath("fileName");
|
||||
std::string content;
|
||||
std::fill_n(std::back_inserter(content), GetParam().mSize, 'a');
|
||||
std::istringstream stream(content);
|
||||
|
|
|
@ -38,10 +38,10 @@ namespace
|
|||
sol::state lua;
|
||||
LuaUtil::InputAction::Registry registry;
|
||||
LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description",
|
||||
sol::make_object(lua, false) });
|
||||
sol::make_object(lua, false), false });
|
||||
registry.insert(a);
|
||||
LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description",
|
||||
sol::make_object(lua, false) });
|
||||
sol::make_object(lua, false), false });
|
||||
registry.insert(b);
|
||||
LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) });
|
||||
LuaUtil::Callback bindBToA(
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <components/misc/strings/conversion.hpp>
|
||||
#include <components/settings/parser.hpp>
|
||||
#include <components/settings/values.hpp>
|
||||
#include <components/testing/util.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
@ -24,5 +25,9 @@ int main(int argc, char** argv)
|
|||
Settings::StaticValues::init();
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Misc
|
|||
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesXZQuat[] = {
|
||||
{
|
||||
osg::Quat(1, 0, 0, 0),
|
||||
osg::Vec3f(0, 0, osg::PI),
|
||||
osg::Vec3f(0, 0, osg::PIf),
|
||||
},
|
||||
{
|
||||
osg::Quat(0, 1, 0, 0),
|
||||
|
@ -59,7 +59,7 @@ namespace Misc
|
|||
},
|
||||
{
|
||||
osg::Quat(0, 0, 1, 0),
|
||||
osg::Vec3f(0, 0, osg::PI),
|
||||
osg::Vec3f(0, 0, osg::PIf),
|
||||
},
|
||||
{
|
||||
osg::Quat(0, 0, 0, 1),
|
||||
|
@ -128,15 +128,15 @@ namespace Misc
|
|||
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesZYXQuat[] = {
|
||||
{
|
||||
osg::Quat(1, 0, 0, 0),
|
||||
osg::Vec3f(osg::PI, 0, 0),
|
||||
osg::Vec3f(osg::PIf, 0, 0),
|
||||
},
|
||||
{
|
||||
osg::Quat(0, 1, 0, 0),
|
||||
osg::Vec3f(osg::PI, 0, osg::PI),
|
||||
osg::Vec3f(osg::PIf, 0, osg::PIf),
|
||||
},
|
||||
{
|
||||
osg::Quat(0, 0, 1, 0),
|
||||
osg::Vec3f(0, 0, osg::PI),
|
||||
osg::Vec3f(0, 0, osg::PIf),
|
||||
},
|
||||
{
|
||||
osg::Quat(0, 0, 0, 1),
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace
|
|||
ShaderManager mManager;
|
||||
ShaderManager::DefineMap mDefines;
|
||||
|
||||
ShaderManagerTest() { mManager.setShaderPath("tests_output"); }
|
||||
ShaderManagerTest() { mManager.setShaderPath(TestingOpenMW::outputDir()); }
|
||||
|
||||
template <class F>
|
||||
void withShaderFile(const std::string& content, F&& f)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QList>
|
||||
#include <QMessageBox>
|
||||
#include <QPair>
|
||||
#include <QProgressDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -351,9 +352,17 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
|||
if (!resourcesVfs.isEmpty())
|
||||
directories.insert(0, { resourcesVfs });
|
||||
|
||||
QIcon containsDataIcon(":/images/openmw-plugin.png");
|
||||
|
||||
QProgressDialog progressBar("Adding data directories", {}, 0, directories.count(), this);
|
||||
progressBar.setWindowModality(Qt::WindowModal);
|
||||
progressBar.setValue(0);
|
||||
|
||||
std::unordered_set<QString> visitedDirectories;
|
||||
for (const Config::SettingValue& currentDir : directories)
|
||||
{
|
||||
progressBar.setValue(progressBar.value() + 1);
|
||||
|
||||
if (!visitedDirectories.insert(currentDir.value).second)
|
||||
continue;
|
||||
|
||||
|
@ -402,7 +411,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
|||
// Add a "data file" icon if the directory contains a content file
|
||||
if (mSelector->containsDataFiles(currentDir.value))
|
||||
{
|
||||
item->setIcon(QIcon(":/images/openmw-plugin.png"));
|
||||
item->setIcon(containsDataIcon);
|
||||
|
||||
tooltip << tr("Contains content file(s)");
|
||||
}
|
||||
|
@ -765,7 +774,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
|
|||
return;
|
||||
|
||||
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())
|
||||
return;
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
set(NAVMESHTOOL
|
||||
set(NAVMESHTOOL_LIB
|
||||
worldspacedata.cpp
|
||||
navmesh.cpp
|
||||
main.cpp
|
||||
)
|
||||
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL})
|
||||
|
||||
add_library(openmw-navmeshtool-lib STATIC
|
||||
${NAVMESHTOOL}
|
||||
)
|
||||
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL_LIB} main.cpp)
|
||||
|
||||
add_library(openmw-navmeshtool-lib STATIC ${NAVMESHTOOL_LIB})
|
||||
|
||||
if (ANDROID)
|
||||
add_library(openmw-navmeshtool SHARED
|
||||
main.cpp
|
||||
)
|
||||
add_library(openmw-navmeshtool SHARED main.cpp)
|
||||
else()
|
||||
openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL})
|
||||
openmw_add_executable(openmw-navmeshtool main.cpp)
|
||||
endif()
|
||||
|
||||
target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib)
|
||||
|
|
|
@ -62,8 +62,6 @@ namespace NavMeshTool
|
|||
|
||||
bpo::options_description makeOptionsDescription()
|
||||
{
|
||||
using Fallback::FallbackMap;
|
||||
|
||||
bpo::options_description result;
|
||||
auto addOption = result.add_options();
|
||||
addOption("help", "print help message");
|
||||
|
@ -225,7 +223,8 @@ namespace NavMeshTool
|
|||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
|
||||
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
|
||||
DetourNavigator::RecastGlobalAllocator::init();
|
||||
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
|
||||
DetourNavigator::Settings navigatorSettings
|
||||
= DetourNavigator::makeSettingsFromSettingsManager(Debug::getRecastMaxLogLevel());
|
||||
navigatorSettings.mRecast.mSwimHeightScale
|
||||
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
|
||||
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
|
||||
#include "view/doc/viewmanager.hpp"
|
||||
|
||||
using namespace Fallback;
|
||||
|
||||
CS::Editor::Editor(int argc, char** argv)
|
||||
: mConfigVariables(readConfiguration())
|
||||
, mSettingsState(mCfgMgr)
|
||||
|
@ -124,7 +122,10 @@ boost::program_options::variables_map CS::Editor::readConfiguration()
|
|||
->default_value(std::vector<std::string>(), "fallback-archive")
|
||||
->multitoken());
|
||||
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");
|
||||
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;
|
||||
|
||||
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
|
||||
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
||||
|
||||
mEncodingName = variables["encoding"].as<std::string>();
|
||||
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
|
||||
|
|
|
@ -1164,23 +1164,30 @@ void CSVDoc::View::onRequestFocus(const std::string& id)
|
|||
QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position)
|
||||
{
|
||||
QScreen* screen = QApplication::screenAt(position);
|
||||
if (screen == nullptr)
|
||||
if (screen)
|
||||
return screen;
|
||||
|
||||
const QList<QScreen*> screens = QApplication::screens();
|
||||
if (screens.isEmpty())
|
||||
throw std::runtime_error("No screens available");
|
||||
|
||||
int closestDistance = std::numeric_limits<int>::max();
|
||||
for (QScreen* candidate : screens)
|
||||
{
|
||||
QPoint clampedPosition = position;
|
||||
const QRect geometry = candidate->geometry();
|
||||
const int dx = position.x() - std::clamp(position.x(), geometry.left(), geometry.right());
|
||||
const int dy = position.y() - std::clamp(position.y(), geometry.top(), geometry.bottom());
|
||||
const int distance = dx * dx + dy * dy;
|
||||
|
||||
// If we failed to find the screen,
|
||||
// clamp negative positions and try again
|
||||
if (clampedPosition.x() <= 0)
|
||||
clampedPosition.setX(0);
|
||||
if (clampedPosition.y() <= 0)
|
||||
clampedPosition.setY(0);
|
||||
|
||||
screen = QApplication::screenAt(clampedPosition);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
screen = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (screen == nullptr)
|
||||
throw std::runtime_error(
|
||||
Misc::StringUtils::format("Can not detect the screen for position [%d, %d]", position.x(), position.y()));
|
||||
screen = screens.first();
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <components/debug/debugging.hpp>
|
||||
#include <components/testing/util.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
@ -7,5 +8,9 @@ int main(int argc, char* argv[])
|
|||
Log::sMinDebugLevel = Debug::getDebugLevel();
|
||||
|
||||
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)
|
||||
, mActivationDistanceOverride(-1)
|
||||
, mGrab(true)
|
||||
, mExportFonts(false)
|
||||
, mRandomSeed(0)
|
||||
, mNewGame(false)
|
||||
, mCfgMgr(configurationManager)
|
||||
, mGlMaxTextureImageUnits(0)
|
||||
{
|
||||
#if SDL_VERSION_ATLEAST(2, 24, 0)
|
||||
SDL_SetHint(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1");
|
||||
#endif
|
||||
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
|
||||
|
||||
Uint32 flags
|
||||
|
@ -807,7 +811,7 @@ void OMW::Engine::prepareEngine()
|
|||
rootNode->addChild(guiRoot);
|
||||
|
||||
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);
|
||||
mEnvironment.setWindowManager(*mWindowManager);
|
||||
|
||||
|
@ -847,7 +851,7 @@ void OMW::Engine::prepareEngine()
|
|||
}
|
||||
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());
|
||||
mWorld->setupPlayer();
|
||||
mWorld->setRandomSeed(mRandomSeed);
|
||||
|
@ -1109,6 +1113,11 @@ void OMW::Engine::setWarningsMode(int mode)
|
|||
mWarningsMode = mode;
|
||||
}
|
||||
|
||||
void OMW::Engine::enableFontExport(bool exportFonts)
|
||||
{
|
||||
mExportFonts = exportFonts;
|
||||
}
|
||||
|
||||
void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame)
|
||||
{
|
||||
mSaveGameFile = savegame;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <filesystem>
|
||||
|
||||
#include <components/compiler/extensions.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm/refid.hpp>
|
||||
#include <components/files/collections.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
@ -171,7 +172,9 @@ namespace OMW
|
|||
// Grab mouse?
|
||||
bool mGrab;
|
||||
|
||||
bool mExportFonts;
|
||||
unsigned int mRandomSeed;
|
||||
Debug::Level mMaxRecastLogLevel = Debug::Error;
|
||||
|
||||
Compiler::Extensions mExtensions;
|
||||
std::unique_ptr<Compiler::Context> mScriptContext;
|
||||
|
@ -180,6 +183,9 @@ namespace OMW
|
|||
Translation::Storage mTranslationDataStorage;
|
||||
bool mNewGame;
|
||||
|
||||
Files::ConfigurationManager& mCfgMgr;
|
||||
int mGlMaxTextureImageUnits;
|
||||
|
||||
// not implemented
|
||||
Engine(const Engine&);
|
||||
Engine& operator=(const Engine&);
|
||||
|
@ -251,14 +257,14 @@ namespace OMW
|
|||
|
||||
void setWarningsMode(int mode);
|
||||
|
||||
void enableFontExport(bool exportFonts);
|
||||
|
||||
/// Set the save game file to load after initialising the engine.
|
||||
void setSaveGameFile(const std::filesystem::path& savegame);
|
||||
|
||||
void setRandomSeed(unsigned int seed);
|
||||
|
||||
private:
|
||||
Files::ConfigurationManager& mCfgMgr;
|
||||
int mGlMaxTextureImageUnits;
|
||||
void setRecastMaxLogLevel(Debug::Level value) { mMaxRecastLogLevel = value; }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,6 @@ extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace Fallback;
|
||||
|
||||
/**
|
||||
* \brief Parses application command line and calls \ref Cfg::ConfigurationManager
|
||||
* 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());
|
||||
|
||||
// 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.setActivationDistanceOverride(variables["activate-dist"].as<int>());
|
||||
engine.enableFontExport(variables["export-fonts"].as<bool>());
|
||||
engine.setRandomSeed(variables["random-seed"].as<unsigned int>());
|
||||
|
||||
return true;
|
||||
|
@ -220,6 +219,8 @@ int runApplication(int argc, char* argv[])
|
|||
Files::ConfigurationManager cfgMgr;
|
||||
std::unique_ptr<OMW::Engine> engine = std::make_unique<OMW::Engine>(cfgMgr);
|
||||
|
||||
engine->setRecastMaxLogLevel(Debug::getRecastMaxLogLevel());
|
||||
|
||||
if (parseOptions(argc, argv, *engine, cfgMgr))
|
||||
{
|
||||
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
|
||||
/// 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;
|
||||
|
||||
|
|
|
@ -363,7 +363,7 @@ namespace MWBase
|
|||
void windowVisibilityChange(bool visible) override = 0;
|
||||
void windowResized(int x, int y) override = 0;
|
||||
void windowClosed() override = 0;
|
||||
virtual bool isWindowVisible() = 0;
|
||||
virtual bool isWindowVisible() const = 0;
|
||||
|
||||
virtual void watchActor(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual MWWorld::Ptr getWatchedActor() const = 0;
|
||||
|
|
|
@ -237,7 +237,12 @@ namespace MWClass
|
|||
bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -236,11 +236,8 @@ namespace MWClass
|
|||
}
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
|
||||
float dist = store.find("fCombatDistance")->mValue.getFloat();
|
||||
if (!weapon.isEmpty())
|
||||
dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
|
||||
|
||||
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
|
||||
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
|
||||
if (result.first.isEmpty()) // Didn't hit anything
|
||||
return true;
|
||||
|
@ -281,6 +278,9 @@ namespace MWClass
|
|||
if (otherstats.isDead()) // Can't hit dead actors
|
||||
return;
|
||||
|
||||
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
|
||||
return;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
victim.getClass().onHit(
|
||||
|
|
|
@ -572,12 +572,8 @@ namespace MWClass
|
|||
weapon = *weaponslot;
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
|
||||
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
|
||||
float dist = fCombatDistance
|
||||
* (!weapon.isEmpty() ? weapon.get<ESM::Weapon>()->mBase->mData.mReach
|
||||
: store.find("fHandToHandReach")->mValue.getFloat());
|
||||
|
||||
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
|
||||
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
|
||||
if (result.first.isEmpty()) // Didn't hit anything
|
||||
return true;
|
||||
|
@ -615,6 +611,9 @@ namespace MWClass
|
|||
if (otherstats.isDead()) // Can't hit dead actors
|
||||
return;
|
||||
|
||||
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
|
||||
return;
|
||||
|
||||
if (ptr == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(victim);
|
||||
|
||||
|
|
|
@ -1343,12 +1343,13 @@ namespace MWGui
|
|||
return codePoint == '\r';
|
||||
}
|
||||
|
||||
// Normal no-break space (0x00A0) is ignored here
|
||||
// because Morrowind compatibility requires us to render its glyph
|
||||
static bool ucsSpace(int codePoint)
|
||||
{
|
||||
switch (codePoint)
|
||||
{
|
||||
case 0x0020: // SPACE
|
||||
case 0x00A0: // NO-BREAK SPACE
|
||||
case 0x1680: // OGHAM SPACE MARK
|
||||
case 0x180E: // MONGOLIAN VOWEL SEPARATOR
|
||||
case 0x2000: // EN QUAD
|
||||
|
@ -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)
|
||||
{
|
||||
switch (codePoint)
|
||||
{
|
||||
case 0x0020: // SPACE
|
||||
// case 0x00A0: // NO-BREAK SPACE
|
||||
case 0x1680: // OGHAM SPACE MARK
|
||||
case 0x180E: // MONGOLIAN VOWEL SEPARATOR
|
||||
case 0x2000: // EN QUAD
|
||||
|
@ -1388,15 +1391,12 @@ namespace MWGui
|
|||
case 0x2004: // THREE-PER-EM SPACE
|
||||
case 0x2005: // FOUR-PER-EM SPACE
|
||||
case 0x2006: // SIX-PER-EM SPACE
|
||||
case 0x2007: // FIGURE SPACE
|
||||
case 0x2008: // PUNCTUATION SPACE
|
||||
case 0x2009: // THIN SPACE
|
||||
case 0x200A: // HAIR SPACE
|
||||
case 0x200B: // ZERO WIDTH SPACE
|
||||
case 0x202F: // NARROW NO-BREAK SPACE
|
||||
case 0x205F: // MEDIUM MATHEMATICAL SPACE
|
||||
case 0x3000: // IDEOGRAPHIC SPACE
|
||||
// case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -666,7 +666,8 @@ namespace MWGui
|
|||
else if (scrollbar)
|
||||
{
|
||||
mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second));
|
||||
size_t range = book->getSize().second - viewHeight;
|
||||
// Scroll range should be >= 2 to enable scrolling and prevent a crash
|
||||
size_t range = std::max(book->getSize().second - viewHeight, size_t(2));
|
||||
mScrollBar->setScrollRange(range);
|
||||
mScrollBar->setScrollPosition(range - 1);
|
||||
mScrollBar->setTrackSize(
|
||||
|
|
|
@ -29,11 +29,26 @@ namespace MWGui
|
|||
{
|
||||
Misc::FrameRateLimiter frameRateLimiter
|
||||
= Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit());
|
||||
const MWBase::WindowManager& windowManager = *MWBase::Environment::get().getWindowManager();
|
||||
bool paused = false;
|
||||
while (mRunning)
|
||||
{
|
||||
// If finished playing, start again
|
||||
if (!mVideo->update())
|
||||
mVideo->playVideo("video\\menu_background.bik");
|
||||
if (windowManager.isWindowVisible())
|
||||
{
|
||||
if (paused)
|
||||
{
|
||||
mVideo->resume();
|
||||
paused = false;
|
||||
}
|
||||
// If finished playing, start again
|
||||
if (!mVideo->update())
|
||||
mVideo->playVideo("video\\menu_background.bik");
|
||||
}
|
||||
else if (!paused)
|
||||
{
|
||||
paused = true;
|
||||
mVideo->pause();
|
||||
}
|
||||
frameRateLimiter.limit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ namespace MWGui
|
|||
WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath,
|
||||
bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding,
|
||||
const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr)
|
||||
bool exportFonts, const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr)
|
||||
: mOldUpdateMask(0)
|
||||
, mOldCullMask(0)
|
||||
, mStore(nullptr)
|
||||
|
@ -215,7 +215,8 @@ namespace MWGui
|
|||
MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag);
|
||||
|
||||
// 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
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
|
||||
|
@ -729,6 +730,9 @@ namespace MWGui
|
|||
return;
|
||||
}
|
||||
|
||||
if (mGuiModes.empty())
|
||||
return;
|
||||
|
||||
GuiModeState& state = mGuiModeStates[mGuiModes.back()];
|
||||
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
|
||||
}
|
||||
|
||||
bool WindowManager::isWindowVisible()
|
||||
bool WindowManager::isWindowVisible() const
|
||||
{
|
||||
return mWindowVisible;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace MWGui
|
|||
WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage,
|
||||
ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders,
|
||||
ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, bool useShaders,
|
||||
Files::ConfigurationManager& cfgMgr);
|
||||
virtual ~WindowManager();
|
||||
|
||||
|
@ -290,7 +290,7 @@ namespace MWGui
|
|||
void windowVisibilityChange(bool visible) override;
|
||||
void windowResized(int x, int y) override;
|
||||
void windowClosed() override;
|
||||
bool isWindowVisible() override;
|
||||
bool isWindowVisible() const override;
|
||||
|
||||
void watchActor(const MWWorld::Ptr& ptr) override;
|
||||
MWWorld::Ptr getWatchedActor() const override;
|
||||
|
|
|
@ -148,7 +148,7 @@ namespace MWLua
|
|||
};
|
||||
}
|
||||
|
||||
sol::table readOnly = LuaUtil::makeReadOnly(api);
|
||||
return context.setTypePackage(readOnly, "openmw_core");
|
||||
sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
|
||||
return context.setTypePackage(readOnlyApi, "openmw_core");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,94 +38,116 @@ namespace MWLua
|
|||
|
||||
sol::table initInputPackage(const Context& context)
|
||||
{
|
||||
sol::object cached = context.getTypePackage("openmw_input");
|
||||
if (cached != sol::nil)
|
||||
return cached;
|
||||
sol::state_view lua = context.sol();
|
||||
{
|
||||
if (lua["openmw_input"] != sol::nil)
|
||||
return lua["openmw_input"];
|
||||
}
|
||||
|
||||
sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent");
|
||||
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) {
|
||||
if (e.sym > 0 && e.sym <= 255)
|
||||
return std::string(1, static_cast<char>(e.sym));
|
||||
else
|
||||
return std::string();
|
||||
context.cachePackage("openmw_input_keyevent", [&lua]() {
|
||||
sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent");
|
||||
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) {
|
||||
if (e.sym > 0 && e.sym <= 255)
|
||||
return std::string(1, static_cast<char>(e.sym));
|
||||
else
|
||||
return std::string();
|
||||
});
|
||||
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
|
||||
keyEvent["withShift"]
|
||||
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
|
||||
keyEvent["withCtrl"]
|
||||
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; });
|
||||
keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
|
||||
keyEvent["withSuper"]
|
||||
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
|
||||
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
|
||||
keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
|
||||
keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; });
|
||||
keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
|
||||
keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
|
||||
|
||||
auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent");
|
||||
touchpadEvent["device"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; });
|
||||
touchpadEvent["finger"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; });
|
||||
touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f {
|
||||
return { e.mX, e.mY };
|
||||
context.cachePackage("openmw_input_touchpadevent", [&lua]() {
|
||||
auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent");
|
||||
touchpadEvent["device"]
|
||||
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; });
|
||||
touchpadEvent["finger"]
|
||||
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; });
|
||||
touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f {
|
||||
return { e.mX, e.mY };
|
||||
});
|
||||
touchpadEvent["pressure"]
|
||||
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
touchpadEvent["pressure"]
|
||||
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
|
||||
|
||||
auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions");
|
||||
inputActions[sol::meta_function::index]
|
||||
= [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; };
|
||||
{
|
||||
auto pairs = [](LuaUtil::InputAction::Registry& registry) {
|
||||
auto next
|
||||
= [](LuaUtil::InputAction::Registry& registry,
|
||||
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> {
|
||||
std::optional<std::string> nextKey(registry.nextKey(key));
|
||||
if (!nextKey.has_value())
|
||||
return sol::nullopt;
|
||||
else
|
||||
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||
context.cachePackage("openmw_input_inputactions", [&lua]() {
|
||||
auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions");
|
||||
inputActions[sol::meta_function::index]
|
||||
= [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; };
|
||||
{
|
||||
auto pairs = [](LuaUtil::InputAction::Registry& registry) {
|
||||
auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key)
|
||||
-> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> {
|
||||
std::optional<std::string> nextKey(registry.nextKey(key));
|
||||
if (!nextKey.has_value())
|
||||
return sol::nullopt;
|
||||
else
|
||||
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||
};
|
||||
return std::make_tuple(next, registry, registry.firstKey());
|
||||
};
|
||||
return std::make_tuple(next, registry, registry.firstKey());
|
||||
};
|
||||
inputActions[sol::meta_function::pairs] = pairs;
|
||||
}
|
||||
inputActions[sol::meta_function::pairs] = pairs;
|
||||
}
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
|
||||
auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo");
|
||||
actionInfo["key"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; });
|
||||
actionInfo["name"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; });
|
||||
actionInfo["description"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; });
|
||||
actionInfo["l10n"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; });
|
||||
actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
|
||||
actionInfo["defaultValue"]
|
||||
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; });
|
||||
context.cachePackage("openmw_input_actioninfo", [&lua]() {
|
||||
auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo");
|
||||
actionInfo["key"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; });
|
||||
actionInfo["name"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; });
|
||||
actionInfo["description"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; });
|
||||
actionInfo["l10n"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; });
|
||||
actionInfo["type"]
|
||||
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
|
||||
actionInfo["defaultValue"]
|
||||
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; });
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
|
||||
auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
|
||||
inputTriggers[sol::meta_function::index]
|
||||
= [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; };
|
||||
{
|
||||
auto pairs = [](LuaUtil::InputTrigger::Registry& registry) {
|
||||
auto next
|
||||
= [](LuaUtil::InputTrigger::Registry& registry,
|
||||
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> {
|
||||
std::optional<std::string> nextKey(registry.nextKey(key));
|
||||
if (!nextKey.has_value())
|
||||
return sol::nullopt;
|
||||
else
|
||||
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||
context.cachePackage("openmw_input_inputtriggers", [&lua]() {
|
||||
auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
|
||||
inputTriggers[sol::meta_function::index]
|
||||
= [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; };
|
||||
{
|
||||
auto pairs = [](LuaUtil::InputTrigger::Registry& registry) {
|
||||
auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key)
|
||||
-> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> {
|
||||
std::optional<std::string> nextKey(registry.nextKey(key));
|
||||
if (!nextKey.has_value())
|
||||
return sol::nullopt;
|
||||
else
|
||||
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||
};
|
||||
return std::make_tuple(next, registry, registry.firstKey());
|
||||
};
|
||||
return std::make_tuple(next, registry, registry.firstKey());
|
||||
};
|
||||
inputTriggers[sol::meta_function::pairs] = pairs;
|
||||
}
|
||||
inputTriggers[sol::meta_function::pairs] = pairs;
|
||||
}
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
|
||||
auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo");
|
||||
triggerInfo["key"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; });
|
||||
triggerInfo["name"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; });
|
||||
triggerInfo["description"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; });
|
||||
triggerInfo["l10n"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; });
|
||||
context.cachePackage("openmw_input_triggerinfo", [&lua]() {
|
||||
auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo");
|
||||
triggerInfo["key"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; });
|
||||
triggerInfo["name"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; });
|
||||
triggerInfo["description"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; });
|
||||
triggerInfo["l10n"] = sol::readonly_property(
|
||||
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; });
|
||||
return sol::table(lua, sol::create);
|
||||
});
|
||||
|
||||
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
||||
sol::table api(lua, sol::create);
|
||||
|
@ -139,16 +161,18 @@ namespace MWLua
|
|||
}));
|
||||
|
||||
api["actions"] = std::ref(context.mLuaManager->inputActions());
|
||||
api["registerAction"] = [manager = context.mLuaManager](sol::table options) {
|
||||
LuaUtil::InputAction::Info parsedOptions;
|
||||
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||
parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>();
|
||||
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||
parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>();
|
||||
manager->inputActions().insert(std::move(parsedOptions));
|
||||
};
|
||||
api["registerAction"]
|
||||
= [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
|
||||
LuaUtil::InputAction::Info parsedOptions;
|
||||
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||
parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>();
|
||||
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||
parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>();
|
||||
parsedOptions.mPersistent = persistent;
|
||||
manager->inputActions().insert(std::move(parsedOptions));
|
||||
};
|
||||
api["bindAction"] = [manager = context.mLuaManager](
|
||||
std::string_view key, const sol::table& callback, sol::table dependencies) {
|
||||
std::vector<std::string_view> parsedDependencies;
|
||||
|
@ -178,14 +202,16 @@ namespace MWLua
|
|||
};
|
||||
|
||||
api["triggers"] = std::ref(context.mLuaManager->inputTriggers());
|
||||
api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) {
|
||||
LuaUtil::InputTrigger::Info parsedOptions;
|
||||
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||
manager->inputTriggers().insert(std::move(parsedOptions));
|
||||
};
|
||||
api["registerTrigger"]
|
||||
= [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
|
||||
LuaUtil::InputTrigger::Info parsedOptions;
|
||||
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||
parsedOptions.mPersistent = persistent;
|
||||
manager->inputTriggers().insert(std::move(parsedOptions));
|
||||
};
|
||||
api["registerTriggerHandler"]
|
||||
= [manager = context.mLuaManager](std::string_view key, const sol::table& callback) {
|
||||
manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback));
|
||||
|
@ -445,8 +471,8 @@ namespace MWLua
|
|||
{ "Tab", SDL_SCANCODE_TAB },
|
||||
}));
|
||||
|
||||
lua["openmw_input"] = LuaUtil::makeReadOnly(api);
|
||||
return lua["openmw_input"];
|
||||
sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
|
||||
return context.setTypePackage(readOnlyApi, "openmw_input");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }]
|
||||
= sol::main_object(value);
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Only global or self scripts can set the value");
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace MWLua
|
|||
{
|
||||
}
|
||||
MWBase::LuaManager::ActorControls mControls;
|
||||
std::map<CachedStat, sol::object> mStatsCache;
|
||||
std::map<CachedStat, sol::main_object> mStatsCache;
|
||||
bool mIsActive;
|
||||
};
|
||||
|
||||
|
|
|
@ -343,6 +343,7 @@ namespace MWLua
|
|||
mPlayerStorage.clearTemporaryAndRemoveCallbacks();
|
||||
mInputActions.clear();
|
||||
mInputTriggers.clear();
|
||||
mQueuedAutoStartedScripts.clear();
|
||||
for (int i = 0; i < 5; ++i)
|
||||
lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0);
|
||||
}
|
||||
|
@ -653,8 +654,8 @@ namespace MWLua
|
|||
MWBase::Environment::get().getL10nManager()->dropCache();
|
||||
mUiResourceManager.clear();
|
||||
mLua.dropScriptCache();
|
||||
mInputActions.clear();
|
||||
mInputTriggers.clear();
|
||||
mInputActions.clear(true);
|
||||
mInputTriggers.clear(true);
|
||||
initConfiguration();
|
||||
|
||||
ESM::LuaScripts globalData;
|
||||
|
|
|
@ -1061,7 +1061,7 @@ namespace MWLua
|
|||
};
|
||||
|
||||
// types.Actor.activeEffects(o):removeEffect(id, ?arg)
|
||||
activeEffectsT["remove"] = [getEffectKey](const ActorActiveEffects& effects, std::string_view idStr,
|
||||
activeEffectsT["remove"] = [getEffectKey, context](const ActorActiveEffects& effects, std::string_view idStr,
|
||||
sol::optional<std::string_view> argStr) {
|
||||
if (!effects.isActor())
|
||||
return;
|
||||
|
@ -1071,12 +1071,14 @@ namespace MWLua
|
|||
|
||||
MWMechanics::EffectKey key = getEffectKey(idStr, argStr);
|
||||
|
||||
// Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a
|
||||
// spell), we still need to use the active spells store to purge this effect from active spells.
|
||||
const auto& ptr = effects.mActor.ptr();
|
||||
context.mLuaManager->addAction([key, effects]() {
|
||||
// Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a
|
||||
// spell), we still need to use the active spells store to purge this effect from active spells.
|
||||
const auto& ptr = effects.mActor.ptr();
|
||||
|
||||
auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells();
|
||||
activeSpells.purgeEffect(ptr, key.mId, key.mArg);
|
||||
auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells();
|
||||
activeSpells.purgeEffect(ptr, key.mId, key.mArg);
|
||||
});
|
||||
};
|
||||
|
||||
// types.Actor.activeEffects(o):set(value, id, ?arg)
|
||||
|
|
|
@ -213,6 +213,7 @@ namespace MWLua
|
|||
{ "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound },
|
||||
{ "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound },
|
||||
{ "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound },
|
||||
{ "TargetPolygonNotFound", DetourNavigator::Status::TargetPolygonNotFound },
|
||||
{ "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed },
|
||||
{ "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed },
|
||||
{ "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed },
|
||||
|
|
|
@ -123,7 +123,8 @@ namespace MWLua
|
|||
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }]
|
||||
= sol::main_object(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -159,7 +160,7 @@ namespace MWLua
|
|||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }]
|
||||
= value;
|
||||
= sol::main_object(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -183,7 +184,8 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }]
|
||||
= sol::main_object(value);
|
||||
}
|
||||
|
||||
sol::object getProgress(const Context& context) const
|
||||
|
@ -204,7 +206,8 @@ namespace MWLua
|
|||
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }]
|
||||
= sol::main_object(value);
|
||||
}
|
||||
|
||||
SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const
|
||||
|
@ -258,7 +261,7 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = sol::main_object(value);
|
||||
}
|
||||
|
||||
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
|
||||
|
@ -318,7 +321,7 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = sol::main_object(value);
|
||||
}
|
||||
|
||||
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
|
||||
|
@ -402,7 +405,7 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = sol::main_object(value);
|
||||
}
|
||||
|
||||
static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
|
||||
|
@ -465,7 +468,8 @@ namespace MWLua
|
|||
{
|
||||
SelfObject* obj = mObject.asSelfObject();
|
||||
addStatUpdateAction(context.mLuaManager, *obj);
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast<int>(mIndex), prop }] = value;
|
||||
obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast<int>(mIndex), prop }]
|
||||
= sol::main_object(value);
|
||||
}
|
||||
|
||||
static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace MWLua
|
|||
[](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); });
|
||||
record["chanceNone"] = sol::readonly_property(
|
||||
[](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); });
|
||||
record["creatures"] = sol::readonly_property([&](const ESM::CreatureLevList& rec) -> sol::table {
|
||||
record["creatures"] = sol::readonly_property([state](const ESM::CreatureLevList& rec) -> sol::table {
|
||||
sol::table res(state, sol::create);
|
||||
for (size_t i = 0; i < rec.mList.size(); ++i)
|
||||
res[LuaUtil::toLuaIndex(i)] = rec.mList[i];
|
||||
|
|
|
@ -168,7 +168,8 @@ namespace MWLua
|
|||
if (index == LuaUi::Layer::count())
|
||||
throw std::logic_error(std::string("Layer not found"));
|
||||
index++;
|
||||
context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
|
||||
context.mLuaManager->addAction(
|
||||
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
|
||||
};
|
||||
layersTable["insertBefore"] = [context](
|
||||
std::string_view beforename, std::string_view name, const sol::object& opt) {
|
||||
|
@ -177,7 +178,8 @@ namespace MWLua
|
|||
size_t index = LuaUi::Layer::indexOf(beforename);
|
||||
if (index == LuaUi::Layer::count())
|
||||
throw std::logic_error(std::string("Layer not found"));
|
||||
context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
|
||||
context.mLuaManager->addAction(
|
||||
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
|
||||
};
|
||||
sol::table layers = LuaUtil::makeReadOnly(layersTable);
|
||||
sol::table layersMeta = layers[sol::metatable_key];
|
||||
|
@ -285,8 +287,9 @@ namespace MWLua
|
|||
return res;
|
||||
};
|
||||
api["_setWindowDisabled"]
|
||||
= [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) {
|
||||
luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); });
|
||||
= [windowManager, luaManager = context.mLuaManager](std::string window, bool disabled) {
|
||||
luaManager->addAction(
|
||||
[=, window = std::move(window)]() { windowManager->setDisabledByLua(window, disabled); });
|
||||
};
|
||||
|
||||
// TODO
|
||||
|
@ -308,7 +311,7 @@ namespace MWLua
|
|||
return res.str();
|
||||
};
|
||||
element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; },
|
||||
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
|
||||
[](LuaUi::Element& element, const sol::main_table& layout) { element.mLayout = layout; });
|
||||
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
|
||||
if (element->mState != LuaUi::Element::Created)
|
||||
return;
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace MWLua
|
|||
Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'.";
|
||||
}
|
||||
|
||||
sol::object readFile(sol::this_state lua, FileHandle& file)
|
||||
sol::object readFile(lua_State* lua, FileHandle& file)
|
||||
{
|
||||
std::ostringstream os;
|
||||
if (file.mFilePtr && file.mFilePtr->peek() != EOF)
|
||||
|
@ -79,7 +79,7 @@ namespace MWLua
|
|||
return sol::make_object<std::string>(lua, std::move(result));
|
||||
}
|
||||
|
||||
sol::object readLineFromFile(sol::this_state lua, FileHandle& file)
|
||||
sol::object readLineFromFile(lua_State* lua, FileHandle& file)
|
||||
{
|
||||
std::string result;
|
||||
if (file.mFilePtr && std::getline(*file.mFilePtr, result))
|
||||
|
@ -91,7 +91,7 @@ namespace MWLua
|
|||
return sol::nil;
|
||||
}
|
||||
|
||||
sol::object readNumberFromFile(sol::this_state lua, Files::IStreamPtr& file)
|
||||
sol::object readNumberFromFile(lua_State* lua, Files::IStreamPtr& file)
|
||||
{
|
||||
double number = 0;
|
||||
if (file && *file >> number)
|
||||
|
@ -100,7 +100,7 @@ namespace MWLua
|
|||
return sol::nil;
|
||||
}
|
||||
|
||||
sol::object readCharactersFromFile(sol::this_state lua, FileHandle& file, size_t count)
|
||||
sol::object readCharactersFromFile(lua_State* lua, FileHandle& file, size_t count)
|
||||
{
|
||||
if (count <= 0 && file.mFilePtr->peek() != EOF)
|
||||
return sol::make_object<std::string>(lua, std::string());
|
||||
|
@ -189,7 +189,7 @@ namespace MWLua
|
|||
|
||||
return seek(lua, self, std::ios_base::cur, off);
|
||||
});
|
||||
handle["lines"] = [](sol::this_state lua, sol::object self) {
|
||||
handle["lines"] = [](sol::this_main_state lua, sol::main_object self) {
|
||||
if (!self.is<FileHandle*>())
|
||||
throw std::runtime_error("self should be a file handle");
|
||||
return sol::as_function([lua, self]() -> sol::object {
|
||||
|
@ -199,7 +199,7 @@ namespace MWLua
|
|||
});
|
||||
};
|
||||
|
||||
api["lines"] = [vfs](sol::this_state lua, std::string_view fileName) {
|
||||
api["lines"] = [vfs](sol::this_main_state lua, std::string_view fileName) {
|
||||
auto normalizedName = VFS::Path::normalizeFilename(fileName);
|
||||
return sol::as_function(
|
||||
[lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable {
|
||||
|
|
|
@ -289,8 +289,9 @@ namespace MWMechanics
|
|||
const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot);
|
||||
if (enchantmentId.empty())
|
||||
continue;
|
||||
const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
|
||||
if (enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
|
||||
const ESM::Enchantment* enchantment
|
||||
= world->getStore().get<ESM::Enchantment>().search(enchantmentId);
|
||||
if (enchantment == nullptr || enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
|
||||
continue;
|
||||
if (std::find_if(mSpells.begin(), mSpells.end(),
|
||||
[&](const ActiveSpellParams& params) {
|
||||
|
|
|
@ -2028,7 +2028,7 @@ namespace MWMechanics
|
|||
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);
|
||||
if (iter != mIndex.end())
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace MWMechanics
|
|||
std::string_view startKey, std::string_view stopKey, bool forceLoop);
|
||||
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
|
||||
void skipAnimation(const MWWorld::Ptr& ptr) const;
|
||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const;
|
||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const;
|
||||
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
|
||||
void persistAnimationStates() const;
|
||||
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted);
|
||||
|
|
|
@ -40,15 +40,15 @@ namespace MWMechanics
|
|||
|
||||
static const std::size_t MAX_IDLE_SIZE = 8;
|
||||
|
||||
const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||
std::string("idle2"),
|
||||
std::string("idle3"),
|
||||
std::string("idle4"),
|
||||
std::string("idle5"),
|
||||
std::string("idle6"),
|
||||
std::string("idle7"),
|
||||
std::string("idle8"),
|
||||
std::string("idle9"),
|
||||
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||
"idle2",
|
||||
"idle3",
|
||||
"idle4",
|
||||
"idle5",
|
||||
"idle6",
|
||||
"idle7",
|
||||
"idle8",
|
||||
"idle9",
|
||||
};
|
||||
|
||||
namespace
|
||||
|
@ -680,7 +680,7 @@ namespace MWMechanics
|
|||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -695,7 +695,7 @@ namespace MWMechanics
|
|||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "aitemporarybase.hpp"
|
||||
|
@ -181,9 +182,7 @@ namespace MWMechanics
|
|||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
||||
|
||||
/// lookup table for converting idleSelect value to groupName
|
||||
static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
|
||||
|
||||
static int OffsetToPreventOvercrowding();
|
||||
static const std::string_view sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1104,10 +1104,10 @@ namespace MWMechanics
|
|||
attackType = ESM::Weapon::AT_Thrust;
|
||||
// We want to avoid hit keys that come out of nowhere (e.g. in the follow animation)
|
||||
// and processing multiple hit keys for a single attack
|
||||
if (mAttackStrength != -1.f)
|
||||
if (mReadyToHit)
|
||||
{
|
||||
charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess);
|
||||
mAttackStrength = -1.f;
|
||||
mReadyToHit = false;
|
||||
}
|
||||
}
|
||||
else if (isRandomAttackAnimation(groupname) && action == "start")
|
||||
|
@ -1153,10 +1153,10 @@ namespace MWMechanics
|
|||
else if (action == "shoot release")
|
||||
{
|
||||
// See notes for melee release above
|
||||
if (mAttackStrength != -1.f)
|
||||
if (mReadyToHit)
|
||||
{
|
||||
mAnimation->releaseArrow(mAttackStrength);
|
||||
mAttackStrength = -1.f;
|
||||
mReadyToHit = false;
|
||||
}
|
||||
}
|
||||
else if (action == "shoot follow attach")
|
||||
|
@ -1246,7 +1246,7 @@ namespace MWMechanics
|
|||
|
||||
void CharacterController::prepareHit()
|
||||
{
|
||||
if (mAttackStrength != -1.f)
|
||||
if (mReadyToHit)
|
||||
return;
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
|
@ -1261,6 +1261,8 @@ namespace MWMechanics
|
|||
mAttackStrength = 0.f;
|
||||
playSwishSound();
|
||||
}
|
||||
|
||||
mReadyToHit = true;
|
||||
}
|
||||
|
||||
bool CharacterController::updateWeaponState()
|
||||
|
@ -1520,6 +1522,7 @@ namespace MWMechanics
|
|||
&& (mHitState == CharState_None || mHitState == CharState_Block))
|
||||
{
|
||||
mAttackStrength = -1.f;
|
||||
mReadyToHit = false;
|
||||
|
||||
// Randomize attacks for non-bipedal creatures
|
||||
if (!cls.isBipedal(mPtr)
|
||||
|
@ -1806,8 +1809,7 @@ namespace MWMechanics
|
|||
stop = strength + ' ' + stop;
|
||||
}
|
||||
|
||||
// Reset attack strength to make extra sure hits that come out of nowhere aren't processed
|
||||
mAttackStrength = -1.f;
|
||||
mReadyToHit = false;
|
||||
|
||||
if (animPlaying)
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
|
|
|
@ -172,6 +172,7 @@ namespace MWMechanics
|
|||
std::string mCurrentWeapon;
|
||||
|
||||
float mAttackStrength{ -1.f };
|
||||
bool mReadyToHit{ false };
|
||||
MWWorld::Ptr mAttackVictim;
|
||||
osg::Vec3f mAttackHitPos;
|
||||
bool mAttackSuccess{ false };
|
||||
|
|
|
@ -578,6 +578,24 @@ namespace MWMechanics
|
|||
return dist;
|
||||
}
|
||||
|
||||
float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon)
|
||||
{
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
|
||||
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
|
||||
if (!weapon.isEmpty())
|
||||
return fCombatDistance * weapon.get<ESM::Weapon>()->mBase->mData.mReach;
|
||||
if (actor.getClass().isNpc())
|
||||
return fCombatDistance * store.find("fHandToHandReach")->mValue.getFloat();
|
||||
return fCombatDistance;
|
||||
}
|
||||
|
||||
bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach)
|
||||
{
|
||||
const float heightDiff = actor.getRefData().getPosition().pos[2] - target.getRefData().getPosition().pos[2];
|
||||
return std::abs(heightDiff) < reach && getDistanceToBounds(actor, target) < reach;
|
||||
}
|
||||
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach)
|
||||
{
|
||||
// Lasciate ogne speranza, voi ch'entrate
|
||||
|
@ -614,11 +632,13 @@ namespace MWMechanics
|
|||
{
|
||||
if (actor == target || target.getClass().getCreatureStats(target).isDead())
|
||||
continue;
|
||||
|
||||
const float dist = getDistanceToBounds(actor, target);
|
||||
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
|
||||
if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach)
|
||||
if (dist >= minDist || !isInMeleeReach(actor, target, reach))
|
||||
continue;
|
||||
|
||||
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
|
||||
|
||||
// Horizontal angle checks.
|
||||
osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() };
|
||||
actorToTargetXY.normalize();
|
||||
|
|
|
@ -64,6 +64,10 @@ namespace MWMechanics
|
|||
// Cursed distance calculation used for combat proximity and hit checks in Morrowind
|
||||
float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||
|
||||
float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon);
|
||||
|
||||
bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach);
|
||||
|
||||
// Similarly cursed hit target selection
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach);
|
||||
|
||||
|
|
|
@ -778,7 +778,8 @@ namespace MWMechanics
|
|||
else
|
||||
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())
|
||||
return mActors.checkAnimationPlaying(ptr, groupName);
|
||||
|
|
|
@ -146,7 +146,7 @@ namespace MWMechanics
|
|||
std::string_view startKey, std::string_view stopKey, bool forceLoop) override;
|
||||
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
|
||||
void skipAnimation(const MWWorld::Ptr& ptr) override;
|
||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override;
|
||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) override;
|
||||
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
|
||||
void persistAnimationStates() override;
|
||||
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override;
|
||||
|
|
|
@ -161,7 +161,7 @@ namespace MWRender
|
|||
|
||||
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||
{
|
||||
if (Settings::game().mShieldSheathing)
|
||||
if (Settings::game().mShieldSheathing && mObjectRoot)
|
||||
{
|
||||
const MWWorld::Class& cls = mPtr.getClass();
|
||||
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);
|
||||
|
|
|
@ -1695,7 +1695,7 @@ namespace MWRender
|
|||
mGlowUpdater->setColor(color);
|
||||
mGlowUpdater->setDuration(glowDuration);
|
||||
}
|
||||
else
|
||||
else if (mObjectRoot)
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration);
|
||||
}
|
||||
}
|
||||
|
@ -1869,7 +1869,7 @@ namespace MWRender
|
|||
|
||||
void Animation::setAlpha(float alpha)
|
||||
{
|
||||
if (alpha == mAlpha)
|
||||
if (alpha == mAlpha || !mObjectRoot)
|
||||
return;
|
||||
mAlpha = alpha;
|
||||
|
||||
|
|
|
@ -260,7 +260,6 @@ namespace MWRender
|
|||
// turn off sky blending
|
||||
stateset->addUniform(new osg::Uniform("far", 10000000.0f));
|
||||
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f));
|
||||
stateset->addUniform(new osg::Uniform("sky", 0));
|
||||
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 }));
|
||||
|
||||
stateset->addUniform(new osg::Uniform("emissiveMult", 1.f));
|
||||
|
|
|
@ -259,7 +259,8 @@ namespace MWRender
|
|||
void CreatureWeaponAnimation::addControllers()
|
||||
{
|
||||
Animation::addControllers();
|
||||
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
|
||||
if (mObjectRoot)
|
||||
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
|
||||
}
|
||||
|
||||
osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration)
|
||||
|
|
|
@ -736,7 +736,6 @@ namespace MWRender
|
|||
// turn of sky blending
|
||||
stateset->addUniform(new osg::Uniform("far", 10000000.0f));
|
||||
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f));
|
||||
stateset->addUniform(new osg::Uniform("sky", 0));
|
||||
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 }));
|
||||
|
||||
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
|
||||
|
|
|
@ -49,11 +49,6 @@ namespace MWRender
|
|||
&& exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute;
|
||||
#endif
|
||||
|
||||
if (mUseCompute)
|
||||
Log(Debug::Info) << "Initialized compute shader pipeline for water ripples";
|
||||
else
|
||||
Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples";
|
||||
|
||||
for (size_t i = 0; i < mState.size(); ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
@ -91,6 +86,18 @@ namespace MWRender
|
|||
else
|
||||
setupFragmentPipeline();
|
||||
|
||||
if (mProgramBlobber != nullptr)
|
||||
{
|
||||
static bool pipelineLogged = [&] {
|
||||
if (mUseCompute)
|
||||
Log(Debug::Info) << "Initialized compute shader pipeline for water ripples";
|
||||
else
|
||||
Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples";
|
||||
return true;
|
||||
}();
|
||||
(void)pipelineLogged;
|
||||
}
|
||||
|
||||
setCullCallback(new osg::NodeCallback);
|
||||
setUpdateCallback(new osg::NodeCallback);
|
||||
}
|
||||
|
@ -102,21 +109,36 @@ namespace MWRender
|
|||
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> blobber
|
||||
= shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT);
|
||||
osg::ref_ptr<osg::Shader> simulate
|
||||
= shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT);
|
||||
if (vertex == nullptr || blobber == nullptr || simulate == nullptr)
|
||||
{
|
||||
Log(Debug::Error) << "Failed to load shaders required for fragment shader ripple pipeline";
|
||||
return;
|
||||
}
|
||||
|
||||
mProgramBlobber = shaderManager.getProgram(
|
||||
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT));
|
||||
mProgramSimulation = shaderManager.getProgram(
|
||||
std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT));
|
||||
mProgramBlobber = shaderManager.getProgram(vertex, std::move(blobber));
|
||||
mProgramSimulation = shaderManager.getProgram(std::move(vertex), std::move(simulate));
|
||||
}
|
||||
|
||||
void RipplesSurface::setupComputePipeline()
|
||||
{
|
||||
auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager();
|
||||
|
||||
mProgramBlobber = shaderManager.getProgram(
|
||||
nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE));
|
||||
mProgramSimulation = shaderManager.getProgram(
|
||||
nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE));
|
||||
osg::ref_ptr<osg::Shader> blobber
|
||||
= shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE);
|
||||
osg::ref_ptr<osg::Shader> simulate
|
||||
= shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE);
|
||||
if (blobber == nullptr || simulate == nullptr)
|
||||
{
|
||||
Log(Debug::Error) << "Failed to load shaders required for compute shader ripple pipeline";
|
||||
return;
|
||||
}
|
||||
|
||||
mProgramBlobber = shaderManager.getProgram(nullptr, std::move(blobber));
|
||||
mProgramSimulation = shaderManager.getProgram(nullptr, std::move(simulate));
|
||||
}
|
||||
|
||||
void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state)
|
||||
|
@ -184,6 +206,9 @@ namespace MWRender
|
|||
|
||||
void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const
|
||||
{
|
||||
if (mProgramBlobber == nullptr || mProgramSimulation == nullptr)
|
||||
return;
|
||||
|
||||
osg::State& state = *renderInfo.getState();
|
||||
const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2;
|
||||
const State& frameState = mState[currentFrame];
|
||||
|
|
|
@ -262,15 +262,15 @@ namespace MWRender
|
|||
, mPrecipitationAlpha(0.f)
|
||||
, mDirtyParticlesEffect(false)
|
||||
{
|
||||
osg::ref_ptr<CameraRelativeTransform> skyroot = new CameraRelativeTransform;
|
||||
skyroot->setName("Sky Root");
|
||||
mSkyRootNode = new CameraRelativeTransform;
|
||||
mSkyRootNode->setName("Sky Root");
|
||||
// Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline
|
||||
if (!mSceneManager->getForceShaders())
|
||||
skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),
|
||||
mSkyRootNode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),
|
||||
osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON);
|
||||
mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false);
|
||||
SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet());
|
||||
parentNode->addChild(skyroot);
|
||||
mSceneManager->setUpNormalsRTForStateSet(mSkyRootNode->getOrCreateStateSet(), false);
|
||||
SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*mSkyRootNode->getOrCreateStateSet());
|
||||
parentNode->addChild(mSkyRootNode);
|
||||
|
||||
mEarlyRenderBinRoot = new osg::Group;
|
||||
// render before the world is rendered
|
||||
|
@ -281,19 +281,18 @@ namespace MWRender
|
|||
if (enableSkyRTT)
|
||||
{
|
||||
mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot);
|
||||
skyroot->addChild(mSkyRTT);
|
||||
mRootNode = new osg::Group;
|
||||
skyroot->addChild(mRootNode);
|
||||
mSkyRootNode->addChild(mSkyRTT);
|
||||
}
|
||||
else
|
||||
mRootNode = skyroot;
|
||||
|
||||
mRootNode->setNodeMask(Mask_Sky);
|
||||
mRootNode->addChild(mEarlyRenderBinRoot);
|
||||
mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot);
|
||||
mSkyNode = new osg::Group;
|
||||
mSkyNode->setNodeMask(Mask_Sky);
|
||||
mSkyNode->addChild(mEarlyRenderBinRoot);
|
||||
mSkyRootNode->addChild(mSkyNode);
|
||||
|
||||
mUnderwaterSwitch = new UnderwaterSwitchCallback(mSkyRootNode);
|
||||
|
||||
mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion;
|
||||
mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(skyroot, parentNode, rootNode, camera);
|
||||
mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(mSkyRootNode, parentNode, rootNode, camera);
|
||||
}
|
||||
|
||||
void SkyManager::create()
|
||||
|
@ -464,7 +463,7 @@ namespace MWRender
|
|||
mRainParticleSystem->setUserValue("particleOcclusion", true);
|
||||
mSceneManager->recreateShaders(mRainNode);
|
||||
|
||||
mRootNode->addChild(mRainNode);
|
||||
mSkyNode->addChild(mRainNode);
|
||||
if (mPrecipitationOcclusion)
|
||||
mPrecipitationOccluder->enable();
|
||||
}
|
||||
|
@ -474,7 +473,7 @@ namespace MWRender
|
|||
if (!mRainNode)
|
||||
return;
|
||||
|
||||
mRootNode->removeChild(mRainNode);
|
||||
mSkyNode->removeChild(mRainNode);
|
||||
mRainNode = nullptr;
|
||||
mPlacer = nullptr;
|
||||
mCounter = nullptr;
|
||||
|
@ -485,10 +484,10 @@ namespace MWRender
|
|||
|
||||
SkyManager::~SkyManager()
|
||||
{
|
||||
if (mRootNode)
|
||||
if (mSkyRootNode)
|
||||
{
|
||||
mRootNode->getParent(0)->removeChild(mRootNode);
|
||||
mRootNode = nullptr;
|
||||
mSkyRootNode->getParent(0)->removeChild(mSkyRootNode);
|
||||
mSkyRootNode = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -595,7 +594,7 @@ namespace MWRender
|
|||
const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u;
|
||||
|
||||
mEarlyRenderBinRoot->setNodeMask(mask);
|
||||
mRootNode->setNodeMask(mask);
|
||||
mSkyNode->setNodeMask(mask);
|
||||
|
||||
if (!enabled && mParticleNode && mParticleEffect)
|
||||
{
|
||||
|
@ -691,7 +690,7 @@ namespace MWRender
|
|||
{
|
||||
if (mParticleNode)
|
||||
{
|
||||
mRootNode->removeChild(mParticleNode);
|
||||
mSkyNode->removeChild(mParticleNode);
|
||||
mParticleNode = nullptr;
|
||||
}
|
||||
if (mRainEffect.empty())
|
||||
|
@ -706,7 +705,7 @@ namespace MWRender
|
|||
mParticleNode = new osg::PositionAttitudeTransform;
|
||||
mParticleNode->addCullCallback(mUnderwaterSwitch);
|
||||
mParticleNode->setNodeMask(Mask_WeatherParticles);
|
||||
mRootNode->addChild(mParticleNode);
|
||||
mSkyNode->addChild(mParticleNode);
|
||||
}
|
||||
|
||||
mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode);
|
||||
|
|
|
@ -118,7 +118,8 @@ namespace MWRender
|
|||
|
||||
osg::Camera* mCamera;
|
||||
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
osg::ref_ptr<CameraRelativeTransform> mSkyRootNode;
|
||||
osg::ref_ptr<osg::Group> mSkyNode;
|
||||
osg::ref_ptr<osg::Group> mEarlyRenderBinRoot;
|
||||
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode;
|
||||
|
|
|
@ -162,11 +162,17 @@ namespace MWScript
|
|||
public:
|
||||
void execute(Interpreter::Runtime& runtime) override
|
||||
{
|
||||
MWWorld::Ptr ptr = R()(runtime);
|
||||
MWWorld::Ptr ptr = R()(runtime, false);
|
||||
|
||||
int index = runtime[0].mInteger;
|
||||
runtime.pop();
|
||||
|
||||
if (ptr.isEmpty())
|
||||
{
|
||||
runtime.push(0);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying(
|
||||
ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index)));
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ MWState::Character* MWState::CharacterManager::getCurrentCharacter()
|
|||
return mCurrent;
|
||||
}
|
||||
|
||||
void MWState::CharacterManager::deleteSlot(const MWState::Character* character, const MWState::Slot* slot)
|
||||
void MWState::CharacterManager::deleteSlot(const MWState::Slot* slot, const MWState::Character*& character)
|
||||
{
|
||||
std::list<Character>::iterator it = findCharacter(character);
|
||||
|
||||
|
@ -51,6 +51,7 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character* character,
|
|||
if (character == mCurrent)
|
||||
mCurrent = nullptr;
|
||||
mCharacters.erase(it);
|
||||
character = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace MWState
|
|||
Character* getCurrentCharacter();
|
||||
///< @note May return null
|
||||
|
||||
void deleteSlot(const MWState::Character* character, const MWState::Slot* slot);
|
||||
void deleteSlot(const MWState::Slot* slot, const Character*& character);
|
||||
|
||||
Character* createCharacter(const std::string& name);
|
||||
///< Create new character within saved game management
|
||||
|
|
|
@ -706,10 +706,10 @@ void MWState::StateManager::quickLoad()
|
|||
void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot)
|
||||
{
|
||||
const std::filesystem::path savePath = slot->mPath;
|
||||
mCharacterManager.deleteSlot(character, slot);
|
||||
mCharacterManager.deleteSlot(slot, character);
|
||||
if (mLastSavegame == savePath)
|
||||
{
|
||||
if (character->begin() != character->end())
|
||||
if (character != nullptr)
|
||||
mLastSavegame = character->begin()->mPath;
|
||||
else
|
||||
mLastSavegame.clear();
|
||||
|
@ -757,12 +757,14 @@ void MWState::StateManager::update(float duration)
|
|||
|
||||
if (mNewGameRequest)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
|
||||
newGame();
|
||||
mNewGameRequest = false;
|
||||
}
|
||||
|
||||
if (mLoadRequest)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
|
||||
loadGame(*mLoadRequest);
|
||||
mLoadRequest = std::nullopt;
|
||||
}
|
||||
|
|
|
@ -337,13 +337,13 @@ namespace
|
|||
|
||||
// helper function for forEachInternal
|
||||
template <class Visitor, class List>
|
||||
bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore* cellStore)
|
||||
bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore& cellStore, bool includeDeleted)
|
||||
{
|
||||
for (typename List::List::iterator iter(list.mList.begin()); iter != list.mList.end(); ++iter)
|
||||
for (auto& v : list.mList)
|
||||
{
|
||||
if (!MWWorld::CellStore::isAccessible(iter->mData, iter->mRef))
|
||||
if (!includeDeleted && !MWWorld::CellStore::isAccessible(v.mData, v.mRef))
|
||||
continue;
|
||||
if (!visitor(MWWorld::Ptr(&*iter, cellStore)))
|
||||
if (!visitor(MWWorld::Ptr(&v, &cellStore)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -399,12 +399,12 @@ namespace MWWorld
|
|||
// listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved
|
||||
// objects are accounted for.
|
||||
template <class Visitor>
|
||||
static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore)
|
||||
static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore, bool includeDeleted)
|
||||
{
|
||||
bool returnValue = true;
|
||||
|
||||
Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) {
|
||||
returnValue = returnValue && forEachImp(visitor, store, &cellStore);
|
||||
Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&](auto& store) {
|
||||
returnValue = returnValue && forEachImp(visitor, store, cellStore, includeDeleted);
|
||||
});
|
||||
|
||||
return returnValue;
|
||||
|
@ -583,11 +583,11 @@ namespace MWWorld
|
|||
mMergedRefsNeedsUpdate = true;
|
||||
}
|
||||
|
||||
void CellStore::updateMergedRefs() const
|
||||
void CellStore::updateMergedRefs(bool includeDeleted) const
|
||||
{
|
||||
mMergedRefs.clear();
|
||||
MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell);
|
||||
CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this));
|
||||
CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this), includeDeleted);
|
||||
visitor.merge();
|
||||
mMergedRefsNeedsUpdate = false;
|
||||
}
|
||||
|
|
|
@ -213,13 +213,13 @@ namespace MWWorld
|
|||
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
|
||||
/// \return Iteration completed?
|
||||
template <class Visitor>
|
||||
bool forEach(Visitor&& visitor)
|
||||
bool forEach(Visitor&& visitor, bool includeDeleted = false)
|
||||
{
|
||||
if (mState != State_Loaded)
|
||||
return false;
|
||||
|
||||
if (mMergedRefsNeedsUpdate)
|
||||
updateMergedRefs();
|
||||
updateMergedRefs(includeDeleted);
|
||||
if (mMergedRefs.empty())
|
||||
return true;
|
||||
|
||||
|
@ -227,7 +227,7 @@ namespace MWWorld
|
|||
|
||||
for (LiveCellRefBase* mergedRef : mMergedRefs)
|
||||
{
|
||||
if (!isAccessible(mergedRef->mData, mergedRef->mRef))
|
||||
if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef))
|
||||
continue;
|
||||
|
||||
if (!visitor(MWWorld::Ptr(mergedRef, this)))
|
||||
|
@ -242,17 +242,17 @@ namespace MWWorld
|
|||
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
|
||||
/// \return Iteration completed?
|
||||
template <class Visitor>
|
||||
bool forEachConst(Visitor&& visitor) const
|
||||
bool forEachConst(Visitor&& visitor, bool includeDeleted = false) const
|
||||
{
|
||||
if (mState != State_Loaded)
|
||||
return false;
|
||||
|
||||
if (mMergedRefsNeedsUpdate)
|
||||
updateMergedRefs();
|
||||
updateMergedRefs(includeDeleted);
|
||||
|
||||
for (const LiveCellRefBase* mergedRef : mMergedRefs)
|
||||
{
|
||||
if (!isAccessible(mergedRef->mData, mergedRef->mRef))
|
||||
if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef))
|
||||
continue;
|
||||
|
||||
if (!visitor(MWWorld::ConstPtr(mergedRef, this)))
|
||||
|
@ -267,28 +267,25 @@ namespace MWWorld
|
|||
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
|
||||
/// \return Iteration completed?
|
||||
template <class T, class Visitor>
|
||||
bool forEachType(Visitor&& visitor)
|
||||
bool forEachType(Visitor&& visitor, bool includeDeleted = false)
|
||||
{
|
||||
if (mState != State_Loaded)
|
||||
return false;
|
||||
|
||||
if (mMergedRefsNeedsUpdate)
|
||||
updateMergedRefs();
|
||||
updateMergedRefs(includeDeleted);
|
||||
if (mMergedRefs.empty())
|
||||
return true;
|
||||
|
||||
mHasState = true;
|
||||
|
||||
CellRefList<T>& list = get<T>();
|
||||
|
||||
for (typename CellRefList<T>::List::iterator it(list.mList.begin()); it != list.mList.end(); ++it)
|
||||
for (LiveCellRefBase& base : get<T>().mList)
|
||||
{
|
||||
LiveCellRefBase* base = &*it;
|
||||
if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end())
|
||||
if (mMovedToAnotherCell.contains(&base))
|
||||
continue;
|
||||
if (!isAccessible(base->mData, base->mRef))
|
||||
if (!includeDeleted && !isAccessible(base.mData, base.mRef))
|
||||
continue;
|
||||
if (!visitor(MWWorld::Ptr(base, this)))
|
||||
if (!visitor(MWWorld::Ptr(&base, this)))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -406,7 +403,7 @@ namespace MWWorld
|
|||
|
||||
/// Repopulate mMergedRefs.
|
||||
void requestMergedRefsUpdate();
|
||||
void updateMergedRefs() const;
|
||||
void updateMergedRefs(bool includeDeleted = false) const;
|
||||
|
||||
// (item, max charge)
|
||||
typedef std::vector<std::pair<LiveCellRefBase*, float>> TRechargingItems;
|
||||
|
|
|
@ -406,8 +406,7 @@ namespace MWWorld
|
|||
template <class T>
|
||||
ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other)
|
||||
{
|
||||
char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible<T, PtrType, void>::value ? 1 : -1];
|
||||
((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR);
|
||||
static_assert(IsConvertible<T, PtrType, void>::value);
|
||||
copy(other);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace MWWorld
|
|||
mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr);
|
||||
reader.setModIndex(index);
|
||||
reader.updateModIndices(mNameToIndex);
|
||||
mStore.loadESM4(reader);
|
||||
mStore.loadESM4(reader, listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -468,9 +468,16 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void ESMStore::loadESM4(ESM4::Reader& reader)
|
||||
void ESMStore::loadESM4(ESM4::Reader& reader, Loading::Listener* listener)
|
||||
{
|
||||
auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); };
|
||||
if (listener != nullptr)
|
||||
listener->setProgressRange(::EsmLoader::fileProgress);
|
||||
auto visitorRec = [this, listener](ESM4::Reader& reader) {
|
||||
bool result = ESMStoreImp::readRecord(reader, *this);
|
||||
if (listener != nullptr)
|
||||
listener->setProgress(::EsmLoader::fileProgress * reader.getFileOffset() / reader.getFileSize());
|
||||
return result;
|
||||
};
|
||||
ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {});
|
||||
}
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ namespace MWWorld
|
|||
void validateDynamic();
|
||||
|
||||
void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue);
|
||||
void loadESM4(ESM4::Reader& esm);
|
||||
void loadESM4(ESM4::Reader& esm, Loading::Listener* listener);
|
||||
|
||||
template <class T>
|
||||
const Store<T>& get() const
|
||||
|
|
|
@ -367,7 +367,7 @@ namespace MWWorld
|
|||
|
||||
ListAndResetObjectsVisitor visitor;
|
||||
|
||||
cell->forEach(visitor);
|
||||
cell->forEach(visitor, true); // Include objects being teleported by Lua
|
||||
for (const auto& ptr : visitor.mObjects)
|
||||
{
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
|
|
|
@ -290,14 +290,14 @@ namespace MWWorld
|
|||
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
}
|
||||
|
||||
void World::init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
|
||||
SceneUtil::UnrefQueue& unrefQueue)
|
||||
void World::init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue)
|
||||
{
|
||||
mPhysics = std::make_unique<MWPhysics::PhysicsSystem>(mResourceSystem, rootNode);
|
||||
|
||||
if (Settings::navigator().mEnable)
|
||||
{
|
||||
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
|
||||
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(maxRecastLogLevel);
|
||||
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
|
||||
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <osg/Timer>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/readerscache.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
@ -201,8 +202,8 @@ namespace MWWorld
|
|||
Loading::Listener* listener);
|
||||
|
||||
// Must be called after `loadData`.
|
||||
void init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue,
|
||||
SceneUtil::UnrefQueue& unrefQueue);
|
||||
void init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue);
|
||||
|
||||
virtual ~World();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <components/misc/strings/conversion.hpp>
|
||||
#include <components/settings/parser.hpp>
|
||||
#include <components/settings/values.hpp>
|
||||
#include <components/testing/util.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
@ -24,5 +25,9 @@ int main(int argc, char* argv[])
|
|||
Settings::StaticValues::init();
|
||||
|
||||
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
|
||||
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
|
||||
shapes/box inputactions yamlloader scripttracker
|
||||
shapes/box inputactions yamlloader scripttracker luastateptr
|
||||
)
|
||||
|
||||
add_component_dir (l10n
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "gamesettings.hpp"
|
||||
|
||||
#include <QDir>
|
||||
#include <QProgressDialog>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
@ -37,8 +38,13 @@ void Config::GameSettings::validatePaths()
|
|||
|
||||
mDataDirs.clear();
|
||||
|
||||
QProgressDialog progressBar("Validating paths", {}, 0, paths.count() + 1);
|
||||
progressBar.setWindowModality(Qt::WindowModal);
|
||||
progressBar.setValue(0);
|
||||
|
||||
for (const auto& dataDir : paths)
|
||||
{
|
||||
progressBar.setValue(progressBar.value() + 1);
|
||||
if (QDir(dataDir.value).exists())
|
||||
{
|
||||
SettingValue copy = dataDir;
|
||||
|
@ -50,6 +56,8 @@ void Config::GameSettings::validatePaths()
|
|||
// Do the same for data-local
|
||||
const QString& local = mSettings.value(QString("data-local")).value;
|
||||
|
||||
progressBar.setValue(progressBar.value() + 1);
|
||||
|
||||
if (!local.isEmpty() && QDir(local).exists())
|
||||
{
|
||||
mDataLocal = QDir(local).canonicalPath();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFont>
|
||||
#include <QIODevice>
|
||||
|
||||
|
@ -78,14 +79,10 @@ ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(int row)
|
|||
}
|
||||
const ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(const QString& name) const
|
||||
{
|
||||
EsmFile::FileProperty fp = EsmFile::FileProperty_FileName;
|
||||
|
||||
if (name.contains('/'))
|
||||
fp = EsmFile::FileProperty_FilePath;
|
||||
|
||||
bool path = name.contains('/');
|
||||
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 nullptr;
|
||||
|
@ -310,7 +307,6 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
|
|||
{
|
||||
setCheckState(file->filePath(), success);
|
||||
emit dataChanged(index, index);
|
||||
checkForLoadOrderErrors();
|
||||
}
|
||||
else
|
||||
return success;
|
||||
|
@ -425,7 +421,6 @@ bool ContentSelectorModel::ContentModel::dropMimeData(
|
|||
|
||||
dataChanged(index(minRow, 0), index(maxRow, 0));
|
||||
// at this point we know that drag and drop has finished.
|
||||
checkForLoadOrderErrors();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -552,15 +547,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
|
|||
|
||||
bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path)
|
||||
{
|
||||
QDir dir(path);
|
||||
QStringList filters;
|
||||
filters << "*.esp"
|
||||
<< "*.esm"
|
||||
<< "*.omwgame"
|
||||
<< "*.omwaddon";
|
||||
dir.setNameFilters(filters);
|
||||
|
||||
return dir.entryList().count() != 0;
|
||||
QDirIterator it(path, filters, QDir::Files | QDir::NoDotAndDotDot);
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::clearFiles()
|
||||
|
@ -707,12 +700,13 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi
|
|||
|
||||
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)
|
||||
{
|
||||
mPluginsWithLoadOrderError.clear();
|
||||
int previousPosition = -1;
|
||||
for (const QString& filepath : fileList)
|
||||
{
|
||||
|
@ -725,7 +719,6 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
|
|||
if (filePosition < previousPosition)
|
||||
{
|
||||
mFiles.move(filePosition, previousPosition);
|
||||
emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -733,24 +726,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
|
|||
}
|
||||
}
|
||||
}
|
||||
checkForLoadOrderErrors();
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::checkForLoadOrderErrors()
|
||||
{
|
||||
for (int row = 0; row < mFiles.count(); ++row)
|
||||
{
|
||||
EsmFile* file = mFiles.at(row);
|
||||
bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0;
|
||||
if (isRowInError)
|
||||
{
|
||||
mPluginsWithLoadOrderError.insert(file->filePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
mPluginsWithLoadOrderError.remove(file->filePath());
|
||||
}
|
||||
}
|
||||
emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
|
||||
}
|
||||
|
||||
QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors(
|
||||
|
@ -791,11 +767,12 @@ QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::
|
|||
|
||||
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>");
|
||||
int index = indexFromItem(item(file->filePath())).row();
|
||||
for (const LoadOrderError& error : checkForLoadOrderErrors(file, index))
|
||||
for (const LoadOrderError& error : errors)
|
||||
{
|
||||
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
|
||||
|
||||
|
@ -900,7 +877,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
|
|||
|
||||
void ContentSelectorModel::ContentModel::uncheckAll()
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
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();
|
||||
|
||||
/// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues
|
||||
void checkForLoadOrderErrors();
|
||||
|
||||
private:
|
||||
void addFile(EsmFile* file);
|
||||
|
||||
|
@ -89,7 +86,6 @@ namespace ContentSelectorModel
|
|||
QStringList mNonUserContent;
|
||||
std::set<const EsmFile*> mCheckedFiles;
|
||||
QHash<QString, bool> mNewFiles;
|
||||
QSet<QString> mPluginsWithLoadOrderError;
|
||||
QString mEncoding;
|
||||
QIcon mWarningIcon;
|
||||
QIcon mErrorIcon;
|
||||
|
|
|
@ -48,18 +48,18 @@ namespace ContentSelectorModel
|
|||
void addGameFile(const QString& name) { mGameFiles.append(name); }
|
||||
QVariant fileProperty(const FileProperty prop) const;
|
||||
|
||||
QString fileName() const { return mFileName; }
|
||||
QString author() const { return mAuthor; }
|
||||
const QString& fileName() const { return mFileName; }
|
||||
const QString& author() const { return mAuthor; }
|
||||
QDateTime modified() const { return mModified; }
|
||||
QString formatVersion() const { return mVersion; }
|
||||
QString filePath() const { return mPath; }
|
||||
const QString& formatVersion() const { return mVersion; }
|
||||
const QString& filePath() const { return mPath; }
|
||||
bool builtIn() const { return mBuiltIn; }
|
||||
bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; }
|
||||
bool isMissing() const { return mPath.isEmpty(); }
|
||||
|
||||
/// @note Contains file names, not paths.
|
||||
const QStringList& gameFiles() const { return mGameFiles; }
|
||||
QString description() const { return mDescription; }
|
||||
const QString& description() const { return mDescription; }
|
||||
QString toolTip() const
|
||||
{
|
||||
if (isMissing())
|
||||
|
|
|
@ -211,7 +211,6 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne
|
|||
ui->gameFileView->setCurrentIndex(0);
|
||||
|
||||
mContentModel->uncheckAll();
|
||||
mContentModel->checkForLoadOrderErrors();
|
||||
}
|
||||
|
||||
void ContentSelectorView::ContentSelector::sortFiles()
|
||||
|
@ -254,7 +253,6 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i
|
|||
oldIndex = index;
|
||||
|
||||
setGameFileSelected(index, true);
|
||||
mContentModel->checkForLoadOrderErrors();
|
||||
}
|
||||
|
||||
emit signalCurrentGamefileIndexChanged(index);
|
||||
|
|
|
@ -237,7 +237,7 @@ namespace Crash
|
|||
// must remain until monitor has finished
|
||||
waitMonitor();
|
||||
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '"
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash dump saved to '"
|
||||
+ Misc::StringUtils::u8StringToString(getCrashDumpPath(*mShm).u8string())
|
||||
+ "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
|
||||
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Crash
|
|||
// the main openmw process in task manager.
|
||||
|
||||
static constexpr const int CrashCatcherTimeout = 2500;
|
||||
static constexpr const int CrashCatcherThawTimeout = 250;
|
||||
|
||||
struct CrashSHM;
|
||||
|
||||
|
|
|
@ -87,9 +87,10 @@ namespace Crash
|
|||
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
|
||||
|
@ -185,7 +186,7 @@ namespace Crash
|
|||
frozen = false;
|
||||
}
|
||||
|
||||
if (!mFreezeAbort && waitApp())
|
||||
if (!mFreezeAbort && waitApp(frozen))
|
||||
{
|
||||
shmLock();
|
||||
|
||||
|
@ -215,7 +216,7 @@ namespace Crash
|
|||
{
|
||||
handleCrash(true);
|
||||
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())
|
||||
+ "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
|
||||
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
|
||||
|
@ -289,10 +290,10 @@ namespace Crash
|
|||
{
|
||||
std::thread messageBoxThread([&]() {
|
||||
SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" };
|
||||
SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW appears to have frozen",
|
||||
"OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW "
|
||||
"hasn't actually frozen, this message box will disappear a within a few seconds of it becoming "
|
||||
"responsive.",
|
||||
SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW has frozen",
|
||||
"OpenMW has frozen. This should never happen. Press Abort to terminate it and generate a crash dump to "
|
||||
"help diagnose the problem.\nOpenMW may unfreeze if you wait, and this message box will disappear "
|
||||
"after it becomes responsive.",
|
||||
1, &button, nullptr };
|
||||
|
||||
int buttonId;
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Crash
|
|||
|
||||
void signalApp() const;
|
||||
|
||||
bool waitApp() const;
|
||||
bool waitApp(bool thawMode) const;
|
||||
|
||||
bool isAppAlive() const;
|
||||
|
||||
|
|
|
@ -106,94 +106,96 @@ namespace Debug
|
|||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
std::string mValue;
|
||||
|
@ -324,22 +326,38 @@ namespace Debug
|
|||
First mFirst;
|
||||
Second mSecond;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
Level toLevel(std::string_view value)
|
||||
{
|
||||
if (value == "ERROR")
|
||||
return Error;
|
||||
if (value == "WARNING")
|
||||
return Warning;
|
||||
if (value == "INFO")
|
||||
return Info;
|
||||
if (value == "VERBOSE")
|
||||
return Verbose;
|
||||
if (value == "DEBUG")
|
||||
return Debug;
|
||||
|
||||
return Verbose;
|
||||
}
|
||||
|
||||
static std::unique_ptr<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)
|
||||
static boost::iostreams::stream_buffer<DebugOutput> sb;
|
||||
static boost::iostreams::stream_buffer<DebugOutput> sb;
|
||||
#else
|
||||
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardOut;
|
||||
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>> bufferedErr;
|
||||
static boost::iostreams::stream_buffer<Tee<Identity, Coloured>> standardOut;
|
||||
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>> bufferedErr;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::ostream& getRawStdout()
|
||||
{
|
||||
|
@ -359,23 +377,19 @@ namespace Debug
|
|||
Level getDebugLevel()
|
||||
{
|
||||
if (const char* env = getenv("OPENMW_DEBUG_LEVEL"))
|
||||
{
|
||||
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 toLevel(env);
|
||||
|
||||
return Verbose;
|
||||
}
|
||||
|
||||
Level getRecastMaxLogLevel()
|
||||
{
|
||||
if (const char* env = getenv("OPENMW_RECAST_MAX_LOG_LEVEL"))
|
||||
return toLevel(env);
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
void setupLogging(const std::filesystem::path& logDir, std::string_view appName)
|
||||
{
|
||||
Log::sMinDebugLevel = getDebugLevel();
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace Debug
|
|||
|
||||
Level getDebugLevel();
|
||||
|
||||
Level getRecastMaxLogLevel();
|
||||
|
||||
// Redirect cout and cerr to the log file
|
||||
void setupLogging(const std::filesystem::path& logDir, std::string_view appName);
|
||||
|
||||
|
|
|
@ -453,9 +453,9 @@ namespace DetourNavigator
|
|||
Misc::setCurrentThreadIdlePriority();
|
||||
while (!mShouldStop)
|
||||
{
|
||||
try
|
||||
if (JobIt job = getNextJob(); job != mJobs.end())
|
||||
{
|
||||
if (JobIt job = getNextJob(); job != mJobs.end())
|
||||
try
|
||||
{
|
||||
const JobStatus status = processJob(*job);
|
||||
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status
|
||||
|
@ -480,12 +480,20 @@ namespace DetourNavigator
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
cleanupLastUpdates();
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to process navmesh job " << job->mId
|
||||
<< " for worldspace=" << job->mWorldspace << " agent=" << job->mAgentBounds
|
||||
<< " changedTile=(" << job->mChangedTile << ")"
|
||||
<< " changeType=" << job->mChangeType
|
||||
<< " by thread=" << std::this_thread::get_id() << ": " << e.what();
|
||||
unlockTile(job->mId, job->mAgentBounds, job->mChangedTile);
|
||||
removeJob(job);
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
@ -493,7 +501,8 @@ namespace DetourNavigator
|
|||
|
||||
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 << ")"
|
||||
<< " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id();
|
||||
|
||||
|
@ -543,7 +552,14 @@ namespace DetourNavigator
|
|||
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
|
||||
= mNavMeshTilesCache.get(job.mAgentBounds, job.mChangedTile, *recastMesh);
|
||||
|
@ -666,12 +682,19 @@ namespace DetourNavigator
|
|||
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;
|
||||
}
|
||||
|
||||
JobIt AsyncNavMeshUpdater::getNextJob()
|
||||
JobIt AsyncNavMeshUpdater::getNextJob() noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
|
@ -746,7 +769,7 @@ namespace DetourNavigator
|
|||
return mJobs.size();
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::cleanupLastUpdates()
|
||||
void AsyncNavMeshUpdater::cleanupLastUpdates() noexcept
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ namespace DetourNavigator
|
|||
inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job,
|
||||
const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh);
|
||||
|
||||
JobIt getNextJob();
|
||||
inline JobIt getNextJob() noexcept;
|
||||
|
||||
void postThreadJob(JobIt job, std::deque<JobIt>& queue);
|
||||
|
||||
|
@ -254,7 +254,7 @@ namespace DetourNavigator
|
|||
|
||||
inline std::size_t getTotalJobs() const;
|
||||
|
||||
void cleanupLastUpdates();
|
||||
inline void cleanupLastUpdates() noexcept;
|
||||
|
||||
inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener);
|
||||
|
||||
|
|
|
@ -523,7 +523,7 @@ namespace DetourNavigator
|
|||
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, ESM::RefId worldspace,
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "recastcontext.hpp"
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "components/debug/debuglog.hpp"
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
|
@ -23,25 +23,30 @@ namespace DetourNavigator
|
|||
return Debug::Debug;
|
||||
}
|
||||
|
||||
std::string formatPrefix(
|
||||
ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds)
|
||||
std::string formatPrefix(ESM::RefId worldspace, const TilePosition& tilePosition,
|
||||
const AgentBounds& agentBounds, const Version& version)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << "Worldspace: " << worldspace << "; tile position: " << tilePosition.x() << ", "
|
||||
<< tilePosition.y() << "; agent bounds: " << agentBounds << "; ";
|
||||
<< tilePosition.y() << "; agent bounds: " << agentBounds << "; version: " << version << "; ";
|
||||
return stream.str();
|
||||
}
|
||||
}
|
||||
|
||||
RecastContext::RecastContext(
|
||||
ESM::RefId worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds)
|
||||
: mPrefix(formatPrefix(worldspace, tilePosition, agentBounds))
|
||||
RecastContext::RecastContext(ESM::RefId worldspace, const TilePosition& tilePosition,
|
||||
const AgentBounds& agentBounds, const Version& version, Debug::Level maxLogLevel)
|
||||
: mMaxLogLevel(maxLogLevel)
|
||||
, mPrefix(formatPrefix(worldspace, tilePosition, agentBounds, version))
|
||||
{
|
||||
}
|
||||
|
||||
void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len)
|
||||
{
|
||||
if (len > 0)
|
||||
Log(getLogLevel(category)) << mPrefix << std::string_view(msg, static_cast<std::size_t>(len));
|
||||
if (msg == nullptr || len <= 0)
|
||||
return;
|
||||
const Debug::Level level = getLogLevel(category);
|
||||
if (level > mMaxLogLevel)
|
||||
return;
|
||||
Log(level) << mPrefix << std::string_view(msg, static_cast<std::size_t>(len));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "tileposition.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm/refid.hpp>
|
||||
|
||||
#include <string>
|
||||
|
@ -12,15 +13,18 @@
|
|||
namespace DetourNavigator
|
||||
{
|
||||
struct AgentBounds;
|
||||
struct Version;
|
||||
|
||||
class RecastContext final : public rcContext
|
||||
{
|
||||
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; }
|
||||
|
||||
private:
|
||||
Debug::Level mMaxLogLevel;
|
||||
std::string mPrefix;
|
||||
|
||||
void doLog(rcLogCategory category, const char* msg, int len) override;
|
||||
|
|
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