mirror of
https://github.com/OpenMW/openmw.git
synced 2025-12-20 20:53:06 +00:00
Merge branch openmw:master into master
This commit is contained in:
commit
59aba3ae7e
135 changed files with 1899 additions and 1006 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
Checks: >
|
Checks: >
|
||||||
-*,
|
-*,
|
||||||
portability-*,
|
portability-*,
|
||||||
|
-portability-template-virtual-member-function,
|
||||||
clang-analyzer-*,
|
clang-analyzer-*,
|
||||||
-clang-analyzer-optin.*,
|
-clang-analyzer-optin.*,
|
||||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
||||||
|
|
@ -16,4 +17,4 @@ CheckOptions:
|
||||||
- key: readability-identifier-naming.NamespaceCase
|
- key: readability-identifier-naming.NamespaceCase
|
||||||
value: CamelCase
|
value: CamelCase
|
||||||
- key: readability-identifier-naming.NamespaceIgnoredRegexp
|
- key: readability-identifier-naming.NamespaceIgnoredRegexp
|
||||||
value: 'osg(DB|FX|Particle|Shadow|Viewer|Util)?'
|
value: 'bpo|osg(DB|FX|Particle|Shadow|Viewer|Util)?'
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,14 @@ variables:
|
||||||
.Ubuntu_Image:
|
.Ubuntu_Image:
|
||||||
tags:
|
tags:
|
||||||
- saas-linux-medium-amd64
|
- saas-linux-medium-amd64
|
||||||
image: ubuntu:22.04
|
image: ubuntu:24.04
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
|
||||||
Ubuntu_GCC_preprocess:
|
Ubuntu_GCC_preprocess:
|
||||||
extends: .Ubuntu_Image
|
extends: .Ubuntu_Image
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_preprocess.ubuntu_22.04.v1
|
key: Ubuntu_GCC_preprocess.ubuntu_24.04.v1
|
||||||
paths:
|
paths:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
- .cache/pip/
|
- .cache/pip/
|
||||||
|
|
@ -43,7 +43,7 @@ Ubuntu_GCC_preprocess:
|
||||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh openmw-deps openmw-deps-dynamic gcc_preprocess
|
- CI/install_debian_deps.sh openmw-deps openmw-deps-dynamic gcc_preprocess
|
||||||
- pip3 install --user click termtables
|
- pip3 install --user --break-system-packages click termtables
|
||||||
script:
|
script:
|
||||||
- CI/ubuntu_gcc_preprocess.sh
|
- CI/ubuntu_gcc_preprocess.sh
|
||||||
rules:
|
rules:
|
||||||
|
|
@ -82,7 +82,7 @@ Ubuntu_GCC_preprocess:
|
||||||
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_benchmark; fi
|
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_benchmark; fi
|
||||||
- ccache -svv
|
- ccache -svv
|
||||||
- df -h
|
- df -h
|
||||||
- if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then gcovr --xml-pretty --exclude-unreachable-branches --print-summary --root "${CI_PROJECT_DIR}" -j $(nproc) -o ../coverage.xml; fi
|
- if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then ~/.local/bin/gcovr --xml-pretty --exclude-unreachable-branches --gcov-ignore-parse-errors=negative_hits.warn_once_per_file --print-summary --root "${CI_PROJECT_DIR}" -j $(nproc) -o ../coverage.xml; fi
|
||||||
- ls | grep -v -e '^extern$' -e '^install$' -e '^components-tests.xml$' -e '^openmw-tests.xml$' -e '^openmw-cs-tests.xml$' | xargs -I '{}' rm -rf './{}'
|
- ls | grep -v -e '^extern$' -e '^install$' -e '^components-tests.xml$' -e '^openmw-tests.xml$' -e '^openmw-cs-tests.xml$' | xargs -I '{}' rm -rf './{}'
|
||||||
- cd ..
|
- cd ..
|
||||||
- df -h
|
- df -h
|
||||||
|
|
@ -97,12 +97,12 @@ Ubuntu_GCC_preprocess:
|
||||||
Coverity:
|
Coverity:
|
||||||
tags:
|
tags:
|
||||||
- saas-linux-medium-amd64
|
- saas-linux-medium-amd64
|
||||||
image: ubuntu:22.04
|
image: ubuntu:24.04
|
||||||
stage: build
|
stage: build
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
cache:
|
cache:
|
||||||
key: Coverity.ubuntu_22.04.v1
|
key: Coverity.ubuntu_24.04.v1
|
||||||
paths:
|
paths:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
- ccache/
|
- ccache/
|
||||||
|
|
@ -141,7 +141,7 @@ Coverity:
|
||||||
Ubuntu_GCC:
|
Ubuntu_GCC:
|
||||||
extends: .Ubuntu
|
extends: .Ubuntu
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC.ubuntu_22.04.v1
|
key: Ubuntu_GCC.ubuntu_24.04.v1
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
||||||
variables:
|
variables:
|
||||||
|
|
@ -154,7 +154,7 @@ Ubuntu_GCC:
|
||||||
Ubuntu_GCC_asan:
|
Ubuntu_GCC_asan:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_asan.ubuntu_22.04.v1
|
key: Ubuntu_GCC_asan.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CMAKE_BUILD_TYPE: Debug
|
CMAKE_BUILD_TYPE: Debug
|
||||||
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak
|
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak
|
||||||
|
|
@ -167,7 +167,7 @@ Clang_Format:
|
||||||
extends: .Ubuntu_Image
|
extends: .Ubuntu_Image
|
||||||
stage: checks
|
stage: checks
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang_Format.ubuntu_22.04.v1
|
key: Ubuntu_Clang_Format.ubuntu_24.04.v1
|
||||||
paths:
|
paths:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
variables:
|
variables:
|
||||||
|
|
@ -183,11 +183,11 @@ Lupdate:
|
||||||
extends: .Ubuntu_Image
|
extends: .Ubuntu_Image
|
||||||
stage: checks
|
stage: checks
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_lupdate.ubuntu_22.04.v1
|
key: Ubuntu_lupdate.ubuntu_24.04.v1
|
||||||
paths:
|
paths:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
variables:
|
variables:
|
||||||
LUPDATE: lupdate
|
LUPDATE: /usr/lib/qt6/bin/lupdate
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh openmw-qt-translations
|
- CI/install_debian_deps.sh openmw-qt-translations
|
||||||
script:
|
script:
|
||||||
|
|
@ -209,7 +209,7 @@ Teal:
|
||||||
Ubuntu_GCC_Debug:
|
Ubuntu_GCC_Debug:
|
||||||
extends: .Ubuntu
|
extends: .Ubuntu
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_Debug.ubuntu_22.04.v2
|
key: Ubuntu_GCC_Debug.ubuntu_24.04.v2
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
||||||
variables:
|
variables:
|
||||||
|
|
@ -225,7 +225,7 @@ Ubuntu_GCC_Debug:
|
||||||
Ubuntu_GCC_tests:
|
Ubuntu_GCC_tests:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -239,7 +239,7 @@ Ubuntu_GCC_tests:
|
||||||
.Ubuntu_GCC_tests_Debug:
|
.Ubuntu_GCC_tests_Debug:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests_Debug.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests_Debug.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -255,7 +255,7 @@ Ubuntu_GCC_tests:
|
||||||
Ubuntu_GCC_tests_asan:
|
Ubuntu_GCC_tests_asan:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests_asan.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests_asan.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -278,7 +278,7 @@ Ubuntu_GCC_tests_asan:
|
||||||
Ubuntu_GCC_tests_ubsan:
|
Ubuntu_GCC_tests_ubsan:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests_ubsan.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests_ubsan.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -298,7 +298,7 @@ Ubuntu_GCC_tests_ubsan:
|
||||||
.Ubuntu_GCC_tests_tsan:
|
.Ubuntu_GCC_tests_tsan:
|
||||||
extends: Ubuntu_GCC
|
extends: Ubuntu_GCC
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests_tsan.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests_tsan.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -316,11 +316,15 @@ Ubuntu_GCC_tests_ubsan:
|
||||||
Ubuntu_GCC_tests_coverage:
|
Ubuntu_GCC_tests_coverage:
|
||||||
extends: .Ubuntu_GCC_tests_Debug
|
extends: .Ubuntu_GCC_tests_Debug
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_tests_coverage.ubuntu_22.04.v1
|
key: Ubuntu_GCC_tests_coverage.ubuntu_24.04.v1
|
||||||
|
paths:
|
||||||
|
- .cache/pip
|
||||||
variables:
|
variables:
|
||||||
BUILD_WITH_CODE_COVERAGE: 1
|
BUILD_WITH_CODE_COVERAGE: 1
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage
|
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage
|
||||||
|
- pipx install gcovr
|
||||||
coverage: /^\s*lines:\s*\d+.\d+\%/
|
coverage: /^\s*lines:\s*\d+.\d+\%/
|
||||||
artifacts:
|
artifacts:
|
||||||
paths: []
|
paths: []
|
||||||
|
|
@ -345,7 +349,7 @@ Ubuntu_GCC_tests_coverage:
|
||||||
- "CI/**/*"
|
- "CI/**/*"
|
||||||
- ".gitlab-ci.yml"
|
- ".gitlab-ci.yml"
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Static_Deps.ubuntu_22.04.v1
|
key: Ubuntu_Static_Deps.ubuntu_24.04.v1
|
||||||
paths:
|
paths:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
- ccache/
|
- ccache/
|
||||||
|
|
@ -362,7 +366,7 @@ Ubuntu_GCC_tests_coverage:
|
||||||
.Ubuntu_Static_Deps_tests:
|
.Ubuntu_Static_Deps_tests:
|
||||||
extends: .Ubuntu_Static_Deps
|
extends: .Ubuntu_Static_Deps
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Static_Deps_tests.ubuntu_22.04.v1
|
key: Ubuntu_Static_Deps_tests.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -381,7 +385,7 @@ Ubuntu_Clang:
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
|
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang.ubuntu_22.04.v2
|
key: Ubuntu_Clang.ubuntu_24.04.v2
|
||||||
variables:
|
variables:
|
||||||
CC: clang
|
CC: clang
|
||||||
CXX: clang++
|
CXX: clang++
|
||||||
|
|
@ -394,7 +398,7 @@ Ubuntu_Clang:
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic
|
- CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang_Tidy.ubuntu_22.04.v1
|
key: Ubuntu_Clang_Tidy.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CMAKE_BUILD_TYPE: Debug
|
CMAKE_BUILD_TYPE: Debug
|
||||||
CMAKE_CXX_FLAGS_DEBUG: -O0
|
CMAKE_CXX_FLAGS_DEBUG: -O0
|
||||||
|
|
@ -451,7 +455,7 @@ Ubuntu_Clang_Tidy_other:
|
||||||
.Ubuntu_Clang_tests:
|
.Ubuntu_Clang_tests:
|
||||||
extends: Ubuntu_Clang
|
extends: Ubuntu_Clang
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang_tests.ubuntu_22.04.v1
|
key: Ubuntu_Clang_tests.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -465,7 +469,7 @@ Ubuntu_Clang_Tidy_other:
|
||||||
Ubuntu_Clang_tests_Debug:
|
Ubuntu_Clang_tests_Debug:
|
||||||
extends: Ubuntu_Clang
|
extends: Ubuntu_Clang
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang_tests_Debug.ubuntu_22.04.v1
|
key: Ubuntu_Clang_tests_Debug.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
@ -489,7 +493,7 @@ Ubuntu_Clang_tests_Debug:
|
||||||
- apt-cache/
|
- apt-cache/
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh $OPENMW_DEPS
|
- CI/install_debian_deps.sh $OPENMW_DEPS
|
||||||
- pip3 install --user numpy matplotlib termtables click
|
- pip3 install --user --break-system-packages numpy matplotlib termtables click
|
||||||
script:
|
script:
|
||||||
- CI/run_integration_tests.sh
|
- CI/run_integration_tests.sh
|
||||||
after_script:
|
after_script:
|
||||||
|
|
@ -500,7 +504,7 @@ Ubuntu_Clang_integration_tests:
|
||||||
needs:
|
needs:
|
||||||
- Ubuntu_Clang
|
- Ubuntu_Clang
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_Clang_integration_tests.ubuntu_22.04.v2
|
key: Ubuntu_Clang_integration_tests.ubuntu_24.04.v2
|
||||||
variables:
|
variables:
|
||||||
OPENMW_DEPS: openmw-integration-tests
|
OPENMW_DEPS: openmw-integration-tests
|
||||||
|
|
||||||
|
|
@ -509,9 +513,9 @@ Ubuntu_GCC_integration_tests_asan:
|
||||||
needs:
|
needs:
|
||||||
- Ubuntu_GCC_asan
|
- Ubuntu_GCC_asan
|
||||||
cache:
|
cache:
|
||||||
key: Ubuntu_GCC_integration_tests_asan.ubuntu_22.04.v1
|
key: Ubuntu_GCC_integration_tests_asan.ubuntu_24.04.v1
|
||||||
variables:
|
variables:
|
||||||
OPENMW_DEPS: openmw-integration-tests libasan6
|
OPENMW_DEPS: openmw-integration-tests libasan
|
||||||
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=0
|
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=0
|
||||||
|
|
||||||
.MacOS:
|
.MacOS:
|
||||||
|
|
@ -990,7 +994,7 @@ Windows_MSBuild_CacheInit:
|
||||||
paths:
|
paths:
|
||||||
- .cache/pip
|
- .cache/pip
|
||||||
before_script:
|
before_script:
|
||||||
- pip3 install --user requests click discord_webhook
|
- pip3 install --user --break-system-packages requests click discord_webhook
|
||||||
script:
|
script:
|
||||||
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt
|
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt
|
||||||
|
|
||||||
|
|
|
||||||
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -2,6 +2,8 @@
|
||||||
------
|
------
|
||||||
|
|
||||||
Bug #2967: Inventory windows don't update when changing items by script
|
Bug #2967: Inventory windows don't update when changing items by script
|
||||||
|
Bug #4437: Transformations for NiSkinInstance are ignored
|
||||||
|
Bug #4885: Disable in dialogue result script causes a crash
|
||||||
Bug #5331: Pathfinding works incorrectly when actor is moved from one interior cell to another
|
Bug #5331: Pathfinding works incorrectly when actor is moved from one interior cell to another
|
||||||
Bug #6039: Next Spell keybind fails while selected enchanted item has multiple copies
|
Bug #6039: Next Spell keybind fails while selected enchanted item has multiple copies
|
||||||
Bug #6573: Editor: Selection behaves incorrectly on high-DPI displays
|
Bug #6573: Editor: Selection behaves incorrectly on high-DPI displays
|
||||||
|
|
@ -9,9 +11,11 @@
|
||||||
Bug #7371: Equipping item from inventory does not play a Down sound when equipping fails
|
Bug #7371: Equipping item from inventory does not play a Down sound when equipping fails
|
||||||
Bug #7622: Player's marksman weapons don't work on close actors underwater
|
Bug #7622: Player's marksman weapons don't work on close actors underwater
|
||||||
Bug #7649: The sound and vfx of resisted enchanted items' magic still play
|
Bug #7649: The sound and vfx of resisted enchanted items' magic still play
|
||||||
|
Bug #7693: I.ItemUsage should return an item to the selected stack if equipping/consumption is denied
|
||||||
Bug #7740: Magic items in the HUD aren't composited correctly
|
Bug #7740: Magic items in the HUD aren't composited correctly
|
||||||
Bug #7799: Picking up ingredients while object paging active grid is on may cause a hiccup
|
Bug #7799: Picking up ingredients while object paging active grid is on may cause a hiccup
|
||||||
Bug #7871: Kwama Queen doesn't start combat with player
|
Bug #7871: Kwama Queen doesn't start combat with player
|
||||||
|
Bug #7979: Paralyzed NPCs battlecry
|
||||||
Bug #8245: The console command ShowVars does not list global mwscripts
|
Bug #8245: The console command ShowVars does not list global mwscripts
|
||||||
Bug #8265: Topics are linked incorrectly
|
Bug #8265: Topics are linked incorrectly
|
||||||
Bug #8303: On target spells cast by non-actors should fire underwater
|
Bug #8303: On target spells cast by non-actors should fire underwater
|
||||||
|
|
@ -25,30 +29,40 @@
|
||||||
Bug #8375: Moon phase cycle doesn't match Morrowind
|
Bug #8375: Moon phase cycle doesn't match Morrowind
|
||||||
Bug #8383: Casting bound helm or boots on beast races doesn't cleanup properly
|
Bug #8383: Casting bound helm or boots on beast races doesn't cleanup properly
|
||||||
Bug #8385: Russian encoding broken with locale parameters and calendar
|
Bug #8385: Russian encoding broken with locale parameters and calendar
|
||||||
|
Bug #8404: Prevent merchant equipping breaks on lights
|
||||||
Bug #8408: OpenMW doesn't report all the potential resting hindrances
|
Bug #8408: OpenMW doesn't report all the potential resting hindrances
|
||||||
Bug #8414: Waterwalking works when collision is disabled
|
Bug #8414: Waterwalking works when collision is disabled
|
||||||
Bug #8431: Behaviour of removed items from a container is buggy
|
Bug #8431: Behaviour of removed items from a container is buggy
|
||||||
Bug #8432: Changing to and from an interior cell doesn't update collision
|
Bug #8432: Changing to and from an interior cell doesn't update collision
|
||||||
|
Bug #8433: Wandering NPCs are not capable of avoiding easy obstacles
|
||||||
Bug #8436: Spell selection in a pinned spellbook window doesn't update
|
Bug #8436: Spell selection in a pinned spellbook window doesn't update
|
||||||
Bug #8437: Pinned inventory window's pin button doesn't look pressed
|
Bug #8437: Pinned inventory window's pin button doesn't look pressed
|
||||||
Bug #8446: Travel prices are strangely inconsistent
|
Bug #8446: Travel prices are strangely inconsistent
|
||||||
|
Bug #8447: Werewolf swimming animation breaks in third person perspective
|
||||||
Bug #8459: Changing magic effect base cost doesn't change spell price
|
Bug #8459: Changing magic effect base cost doesn't change spell price
|
||||||
Bug #8466: Showmap "" reveals nameless cells
|
Bug #8466: Showmap "" reveals nameless cells
|
||||||
Bug #8485: Witchwither disease and probably other common diseases don't work correctly
|
Bug #8485: Witchwither disease and probably other common diseases don't work correctly
|
||||||
Bug #8490: Normals on Water disappear when Water Shader is Enabled but Refraction is Disabled
|
Bug #8490: Normals on Water disappear when Water Shader is Enabled but Refraction is Disabled
|
||||||
Bug #8500: OpenMW Alarm behaviour doesn't match morrowind.exe
|
Bug #8500: OpenMW Alarm behaviour doesn't match morrowind.exe
|
||||||
Bug #8519: Multiple bounty is sometimes assigned to player when detected during a pickpocketing action
|
Bug #8519: Multiple bounty is sometimes assigned to player when detected during a pickpocketing action
|
||||||
|
Bug #8540: Magic resistance is applied to effects without a magnitude
|
||||||
|
Bug #8557: Charm's disposition changes capped on 100, uncapped below 0
|
||||||
|
Bug #8582: addScript-attached local scripts start out inactive
|
||||||
Bug #8585: Dialogue topic list doesn't have enough padding
|
Bug #8585: Dialogue topic list doesn't have enough padding
|
||||||
Bug #8587: Minor INI importer problems
|
Bug #8587: Minor INI importer problems
|
||||||
Bug #8593: Render targets do not generate mipmaps
|
Bug #8593: Render targets do not generate mipmaps
|
||||||
Bug #8598: Post processing shaders don't interact with the vfs correctly
|
Bug #8598: Post processing shaders don't interact with the vfs correctly
|
||||||
Bug #8599: Non-ASCII paths in BSA files don't work
|
Bug #8599: Non-ASCII paths in BSA files don't work
|
||||||
|
Bug #8606: Floating point imprecision can mess with container capacity
|
||||||
Bug #8609: The crosshair is too large
|
Bug #8609: The crosshair is too large
|
||||||
Bug #8610: Terrain normal maps using NormalGL format instead of NormalDX
|
Bug #8610: Terrain normal maps using NormalGL format instead of NormalDX
|
||||||
Bug #8612: Using aiactivate on an ingredient when graphical herbalism is enabled triggers non-stop pickup sounds
|
Bug #8612: Using aiactivate on an ingredient when graphical herbalism is enabled triggers non-stop pickup sounds
|
||||||
|
Bug #8614: Lua garbage collection fails to remove unused data
|
||||||
Bug #8615: Rest/wait time progress speed is different from vanilla
|
Bug #8615: Rest/wait time progress speed is different from vanilla
|
||||||
Feature #2522: Support quick item transfer
|
Feature #2522: Support quick item transfer
|
||||||
Feature #3769: Allow GetSpellEffects on enchantments
|
Feature #3769: Allow GetSpellEffects on enchantments
|
||||||
|
Feature #6976: [Lua] Weather API
|
||||||
|
Feature #8077: Save settings changes when clicking "ok"/closing the window
|
||||||
Feature #8112: Expose landscape record data to Lua
|
Feature #8112: Expose landscape record data to Lua
|
||||||
Feature #8113: Support extended selection in autodetected subdirectory dialog
|
Feature #8113: Support extended selection in autodetected subdirectory dialog
|
||||||
Feature #8139: Editor: Redesign the selection markers
|
Feature #8139: Editor: Redesign the selection markers
|
||||||
|
|
@ -57,8 +71,10 @@
|
||||||
Feature #8320: Add access mwscript source text to lua api
|
Feature #8320: Add access mwscript source text to lua api
|
||||||
Feature #8334: Lua: AddTopic equivalent
|
Feature #8334: Lua: AddTopic equivalent
|
||||||
Feature #8355: Lua: Window visibility checking in interfaces.UI
|
Feature #8355: Lua: Window visibility checking in interfaces.UI
|
||||||
|
Feature #8509: FillJournal script instruction
|
||||||
Feature #8580: Sort characters in the save loading menu
|
Feature #8580: Sort characters in the save loading menu
|
||||||
Feature #8597: Lua: Add more built-in event handlers
|
Feature #8597: Lua: Add more built-in event handlers
|
||||||
|
Feature #8629: Expose path grid data to Lua
|
||||||
|
|
||||||
0.49.0
|
0.49.0
|
||||||
------
|
------
|
||||||
|
|
|
||||||
|
|
@ -708,17 +708,12 @@ printf "Qt ${QT_VER}... "
|
||||||
DLLSUFFIX=""
|
DLLSUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${QT_MAJOR_VER}" -eq 6 ]; then
|
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll
|
||||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll
|
|
||||||
|
|
||||||
# Since Qt 6.7.0 plugin is called "qmodernwindowsstyle"
|
# Since Qt 6.7.0 plugin is called "qmodernwindowsstyle"
|
||||||
if [ "${QT_MINOR_VER}" -ge 7 ]; then
|
if [ "${QT_MINOR_VER}" -ge 7 ]; then
|
||||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
|
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
|
||||||
else
|
|
||||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll
|
|
||||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ declare -rA GROUPED_DEPS=(
|
||||||
libboost-system-dev libboost-iostreams-dev
|
libboost-system-dev libboost-iostreams-dev
|
||||||
|
|
||||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
|
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
|
||||||
libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev
|
libsdl2-dev libqt6opengl6-dev qt6-tools-dev qt6-tools-dev-tools libopenal-dev
|
||||||
libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev
|
libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev
|
||||||
libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev
|
libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev
|
||||||
libyaml-cpp-dev libqt5svg5 libqt5svg5-dev
|
libyaml-cpp-dev libqt6svg6 libqt6svg6-dev
|
||||||
"
|
"
|
||||||
|
|
||||||
# These dependencies can alternatively be built and linked statically.
|
# These dependencies can alternatively be built and linked statically.
|
||||||
|
|
@ -57,22 +57,22 @@ declare -rA GROUPED_DEPS=(
|
||||||
libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev
|
libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev
|
||||||
"
|
"
|
||||||
|
|
||||||
[openmw-coverage]="gcovr"
|
[openmw-coverage]="pipx"
|
||||||
|
|
||||||
[openmw-integration-tests]="
|
[openmw-integration-tests]="
|
||||||
ca-certificates
|
ca-certificates
|
||||||
gdb
|
gdb
|
||||||
git
|
git
|
||||||
git-lfs
|
git-lfs
|
||||||
libavcodec58
|
libavcodec60
|
||||||
libavformat58
|
libavformat60
|
||||||
libavutil56
|
libavutil58
|
||||||
libboost-iostreams1.74.0
|
libboost-iostreams1.83.0
|
||||||
libboost-program-options1.74.0
|
libboost-program-options1.83.0
|
||||||
libboost-system1.74.0
|
libboost-system1.83.0
|
||||||
libbullet3.24
|
libbullet3.24
|
||||||
libcollada-dom2.5-dp0
|
libcollada-dom2.5-dp0
|
||||||
libicu70
|
libicu74
|
||||||
libjpeg8
|
libjpeg8
|
||||||
libluajit-5.1-2
|
libluajit-5.1-2
|
||||||
liblz4-1
|
liblz4-1
|
||||||
|
|
@ -80,19 +80,19 @@ declare -rA GROUPED_DEPS=(
|
||||||
libopenal1
|
libopenal1
|
||||||
libopenscenegraph161
|
libopenscenegraph161
|
||||||
libpng16-16
|
libpng16-16
|
||||||
libqt5opengl5
|
libqt6opengl6
|
||||||
librecast1
|
librecast1
|
||||||
libsdl2-2.0-0
|
libsdl2-2.0-0
|
||||||
libsqlite3-0
|
libsqlite3-0
|
||||||
libswresample3
|
libswresample4
|
||||||
libswscale5
|
libswscale7
|
||||||
libtinyxml2.6.2v5
|
libtinyxml2.6.2v5
|
||||||
libyaml-cpp0.8
|
libyaml-cpp0.8
|
||||||
python3-pip
|
python3-pip
|
||||||
xvfb
|
xvfb
|
||||||
"
|
"
|
||||||
|
|
||||||
[libasan6]="libasan6"
|
[libasan]="libasan8"
|
||||||
|
|
||||||
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
|
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
|
||||||
|
|
||||||
|
|
@ -102,8 +102,8 @@ declare -rA GROUPED_DEPS=(
|
||||||
"
|
"
|
||||||
|
|
||||||
[openmw-qt-translations]="
|
[openmw-qt-translations]="
|
||||||
qttools5-dev
|
qt6-tools-dev
|
||||||
qttools5-dev-tools
|
qt6-tools-dev-tools
|
||||||
git-core
|
git-core
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
|
||||||
set(OPENMW_VERSION_MAJOR 0)
|
set(OPENMW_VERSION_MAJOR 0)
|
||||||
set(OPENMW_VERSION_MINOR 50)
|
set(OPENMW_VERSION_MINOR 50)
|
||||||
set(OPENMW_VERSION_RELEASE 0)
|
set(OPENMW_VERSION_RELEASE 0)
|
||||||
set(OPENMW_LUA_API_REVISION 83)
|
set(OPENMW_LUA_API_REVISION 85)
|
||||||
set(OPENMW_POSTPROCESSING_API_REVISION 3)
|
set(OPENMW_POSTPROCESSING_API_REVISION 3)
|
||||||
|
|
||||||
set(OPENMW_VERSION_COMMITHASH "")
|
set(OPENMW_VERSION_COMMITHASH "")
|
||||||
|
|
@ -249,12 +249,8 @@ endif()
|
||||||
find_package(LZ4 REQUIRED)
|
find_package(LZ4 REQUIRED)
|
||||||
|
|
||||||
if (USE_QT)
|
if (USE_QT)
|
||||||
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
|
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6)
|
||||||
if (QT_VERSION_MAJOR VERSION_EQUAL 5)
|
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED)
|
||||||
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED)
|
|
||||||
else()
|
|
||||||
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED)
|
|
||||||
endif()
|
|
||||||
message(STATUS "Using Qt${QT_VERSION}")
|
message(STATUS "Using Qt${QT_VERSION}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ namespace
|
||||||
|
|
||||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::NavMeshNotFound);
|
Status::NavMeshNotFound);
|
||||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
|
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +147,7 @@ namespace
|
||||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
||||||
{
|
{
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::StartPolygonNotFound);
|
Status::StartPolygonNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ namespace
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
mNavigator->removeAgent(mAgentBounds);
|
mNavigator->removeAgent(mAgentBounds);
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::StartPolygonNotFound);
|
Status::StartPolygonNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ namespace
|
||||||
updateGuard.reset();
|
updateGuard.reset();
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -194,7 +194,7 @@ namespace
|
||||||
updateGuard.reset();
|
updateGuard.reset();
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
||||||
|
|
@ -218,7 +218,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -237,7 +237,7 @@ namespace
|
||||||
|
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mOut = std::back_inserter(mPath);
|
mOut = std::back_inserter(mPath);
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -265,7 +265,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -285,7 +285,7 @@ namespace
|
||||||
|
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mOut = std::back_inserter(mPath);
|
mOut = std::back_inserter(mPath);
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -318,7 +318,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -386,7 +386,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -421,7 +421,7 @@ namespace
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
mEnd.z() = 300;
|
mEnd.z() = 300;
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -453,8 +453,8 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
{}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -487,8 +487,8 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
{}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -520,7 +520,7 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -549,7 +549,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -577,7 +577,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -658,7 +658,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -781,7 +781,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -806,7 +806,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
Status::PartialPath);
|
Status::PartialPath);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -834,7 +834,7 @@ namespace
|
||||||
|
|
||||||
const float endTolerance = 1000.0f;
|
const float endTolerance = 1000.0f;
|
||||||
|
|
||||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut),
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, {}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -979,6 +979,146 @@ namespace
|
||||||
EXPECT_EQ(usedNavMeshTiles, 854);
|
EXPECT_EQ(usedNavMeshTiles, 854);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_mountains)
|
||||||
|
{
|
||||||
|
const std::array<float, 5 * 5> heightfieldData{ {
|
||||||
|
0, 0, 0, 0, 0, // row 0
|
||||||
|
0, 0, 0, 0, 0, // row 1
|
||||||
|
0, 0, 1000, 0, 0, // row 2
|
||||||
|
0, 0, 1000, 0, 0, // row 3
|
||||||
|
0, 0, 0, 0, 0, // row 4
|
||||||
|
} };
|
||||||
|
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||||
|
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
|
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||||
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
|
const osg::Vec3f start(56, 56, 12);
|
||||||
|
const osg::Vec3f end(464, 464, 12);
|
||||||
|
|
||||||
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
|
Status::Success);
|
||||||
|
|
||||||
|
EXPECT_THAT(mPath,
|
||||||
|
ElementsAre( //
|
||||||
|
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||||
|
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||||
|
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||||
|
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||||
|
<< mPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_cliffs)
|
||||||
|
{
|
||||||
|
const std::array<float, 5 * 5> heightfieldData{ {
|
||||||
|
0, 0, 0, 0, 0, // row 0
|
||||||
|
0, 0, 0, 0, 0, // row 1
|
||||||
|
0, 0, -1000, 0, 0, // row 2
|
||||||
|
0, 0, -1000, 0, 0, // row 3
|
||||||
|
0, 0, 0, 0, 0, // row 4
|
||||||
|
} };
|
||||||
|
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||||
|
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
|
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||||
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
|
const osg::Vec3f start(56, 56, 12);
|
||||||
|
const osg::Vec3f end(464, 464, 12);
|
||||||
|
|
||||||
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||||
|
Status::Success);
|
||||||
|
|
||||||
|
EXPECT_THAT(mPath,
|
||||||
|
ElementsAre( //
|
||||||
|
Vec3fEq(56.66664886474609375, 56.66664886474609375, 8.66659259796142578125),
|
||||||
|
Vec3fEq(385.33331298828125, 79.33331298828125, 8.66659259796142578125),
|
||||||
|
Vec3fEq(430.666656494140625, 124.66664886474609375, 8.66659259796142578125),
|
||||||
|
Vec3fEq(463.999969482421875, 463.999969482421875, 8.66659259796142578125)))
|
||||||
|
<< mPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_with_checkpoints)
|
||||||
|
{
|
||||||
|
const std::array<float, 5 * 5> heightfieldData{ {
|
||||||
|
0, 0, 0, 0, 0, // row 0
|
||||||
|
0, 0, 0, 0, 0, // row 1
|
||||||
|
0, 0, 1000, 0, 0, // row 2
|
||||||
|
0, 0, 1000, 0, 0, // row 3
|
||||||
|
0, 0, 0, 0, 0, // row 4
|
||||||
|
} };
|
||||||
|
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||||
|
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
|
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||||
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
|
const std::vector<osg::Vec3f> checkpoints = {
|
||||||
|
osg::Vec3f(400, 70, 12),
|
||||||
|
};
|
||||||
|
|
||||||
|
const osg::Vec3f start(56, 56, 12);
|
||||||
|
const osg::Vec3f end(464, 464, 12);
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||||
|
Status::Success);
|
||||||
|
|
||||||
|
EXPECT_THAT(mPath,
|
||||||
|
ElementsAre( //
|
||||||
|
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||||
|
Vec3fEq(400, 70, 11.33333301544189453125),
|
||||||
|
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||||
|
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||||
|
<< mPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, find_path_should_skip_unreachable_checkpoints)
|
||||||
|
{
|
||||||
|
const std::array<float, 5 * 5> heightfieldData{ {
|
||||||
|
0, 0, 0, 0, 0, // row 0
|
||||||
|
0, 0, 0, 0, 0, // row 1
|
||||||
|
0, 0, 1000, 0, 0, // row 2
|
||||||
|
0, 0, 1000, 0, 0, // row 3
|
||||||
|
0, 0, 0, 0, 0, // row 4
|
||||||
|
} };
|
||||||
|
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||||
|
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
|
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||||
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
|
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||||
|
|
||||||
|
const std::vector<osg::Vec3f> checkpoints = {
|
||||||
|
osg::Vec3f(400, 70, 10000),
|
||||||
|
osg::Vec3f(256, 256, 1000),
|
||||||
|
osg::Vec3f(-1000, -1000, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
const osg::Vec3f start(56, 56, 12);
|
||||||
|
const osg::Vec3f end(464, 464, 12);
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||||
|
Status::Success);
|
||||||
|
|
||||||
|
EXPECT_THAT(mPath,
|
||||||
|
ElementsAre( //
|
||||||
|
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||||
|
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||||
|
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||||
|
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||||
|
<< mPath;
|
||||||
|
}
|
||||||
|
|
||||||
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>
|
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
|
||||||
// Create the file if it doesn't already exist, else the importer will fail
|
// Create the file if it doesn't already exist, else the importer will fail
|
||||||
auto path = mCfgMgr.getUserConfigPath();
|
auto path = mCfgMgr.getUserConfigPath();
|
||||||
path /= "openmw.cfg";
|
path /= "openmw.cfg";
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
#else
|
|
||||||
QFile file(Files::pathToQString(path));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -497,11 +497,7 @@ bool Launcher::MainDialog::writeSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game settings
|
// Game settings
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
QFile file(userPath / Files::openmwCfgFile);
|
QFile file(userPath / Files::openmwCfgFile);
|
||||||
#else
|
|
||||||
QFile file(Files::getUserConfigPathQString(mCfgMgr));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadWrite | QIODevice::Text))
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -163,8 +163,6 @@ bool Launcher::SettingsPage::loadSettings()
|
||||||
loadSettingInt(Settings::physics().mAsyncNumThreads, *physicsThreadsSpinBox);
|
loadSettingInt(Settings::physics().mAsyncNumThreads, *physicsThreadsSpinBox);
|
||||||
loadSettingBool(
|
loadSettingBool(
|
||||||
Settings::game().mAllowActorsToFollowOverWaterSurface, *allowNPCToFollowOverWaterSurfaceCheckBox);
|
Settings::game().mAllowActorsToFollowOverWaterSurface, *allowNPCToFollowOverWaterSurfaceCheckBox);
|
||||||
loadSettingBool(
|
|
||||||
Settings::game().mUnarmedCreatureAttacksDamageArmor, *unarmedCreatureAttacksDamageArmorCheckBox);
|
|
||||||
loadSettingInt(Settings::game().mActorCollisionShapeType, *actorCollisonShapeTypeComboBox);
|
loadSettingInt(Settings::game().mActorCollisionShapeType, *actorCollisonShapeTypeComboBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,8 +374,6 @@ void Launcher::SettingsPage::saveSettings()
|
||||||
saveSettingInt(*physicsThreadsSpinBox, Settings::physics().mAsyncNumThreads);
|
saveSettingInt(*physicsThreadsSpinBox, Settings::physics().mAsyncNumThreads);
|
||||||
saveSettingBool(
|
saveSettingBool(
|
||||||
*allowNPCToFollowOverWaterSurfaceCheckBox, Settings::game().mAllowActorsToFollowOverWaterSurface);
|
*allowNPCToFollowOverWaterSurfaceCheckBox, Settings::game().mAllowActorsToFollowOverWaterSurface);
|
||||||
saveSettingBool(
|
|
||||||
*unarmedCreatureAttacksDamageArmorCheckBox, Settings::game().mUnarmedCreatureAttacksDamageArmor);
|
|
||||||
saveSettingInt(*actorCollisonShapeTypeComboBox, Settings::game().mActorCollisionShapeType);
|
saveSettingInt(*actorCollisonShapeTypeComboBox, Settings::game().mActorCollisionShapeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="10" column="1">
|
<item row="9" column="1">
|
||||||
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
|
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html></string>
|
<string><html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html></string>
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="8" column="1">
|
||||||
<widget class="QCheckBox" name="classicCalmSpellsCheckBox">
|
<widget class="QCheckBox" name="classicCalmSpellsCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html></string>
|
<string><html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html></string>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="12" column="1">
|
<item row="11" column="1">
|
||||||
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
|
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html></string>
|
<string><html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html></string>
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="QCheckBox" name="requireAppropriateAmmunitionCheckBox">
|
<widget class="QCheckBox" name="requireAppropriateAmmunitionCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html></string>
|
<string><html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html></string>
|
||||||
|
|
@ -133,7 +133,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="13" column="1">
|
<item row="12" column="1">
|
||||||
<widget class="QCheckBox" name="graphicHerbalismCheckBox">
|
<widget class="QCheckBox" name="graphicHerbalismCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html></string>
|
<string><html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html></string>
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="11" column="1">
|
<item row="10" column="1">
|
||||||
<widget class="QCheckBox" name="swimUpwardCorrectionCheckBox">
|
<widget class="QCheckBox" name="swimUpwardCorrectionCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html></string>
|
<string><html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html></string>
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="QCheckBox" name="enchantedWeaponsMagicalCheckBox">
|
<widget class="QCheckBox" name="enchantedWeaponsMagicalCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html></string>
|
<string><html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html></string>
|
||||||
|
|
@ -183,7 +183,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
|
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html></string>
|
<string><html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html></string>
|
||||||
|
|
@ -203,7 +203,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox">
|
<widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html></string>
|
<string><html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html></string>
|
||||||
|
|
@ -213,16 +213,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QCheckBox" name="unarmedCreatureAttacksDamageArmorCheckBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Unarmed Creature Attacks Damage Armor</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace sfs = std::filesystem;
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// from configfileparser.cpp
|
// from configfileparser.cpp
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include <components/files/conversion.hpp>
|
#include <components/files/conversion.hpp>
|
||||||
|
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
namespace sfs = std::filesystem;
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
|
|
|
||||||
|
|
@ -240,11 +240,7 @@ target_link_libraries(openmw-cs-lib
|
||||||
components_qt
|
components_qt
|
||||||
)
|
)
|
||||||
|
|
||||||
if (QT_VERSION_MAJOR VERSION_EQUAL 6)
|
target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg)
|
||||||
target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg)
|
|
||||||
else()
|
|
||||||
target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::Svg)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest)
|
target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest)
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@ bool CSMFilter::TextNode::test(const CSMWorld::IdTableBase& table, int row, cons
|
||||||
|
|
||||||
QString string;
|
QString string;
|
||||||
|
|
||||||
if (data.type() == QVariant::String)
|
if (data.typeId() == QMetaType::QString)
|
||||||
{
|
{
|
||||||
string = data.toString();
|
string = data.toString();
|
||||||
}
|
}
|
||||||
else if ((data.type() == QVariant::Int || data.type() == QVariant::UInt)
|
else if ((data.typeId() == QMetaType::Int || data.typeId() == QMetaType::UInt)
|
||||||
&& CSMWorld::Columns::hasEnums(static_cast<CSMWorld::Columns::ColumnId>(mColumnId)))
|
&& CSMWorld::Columns::hasEnums(static_cast<CSMWorld::Columns::ColumnId>(mColumnId)))
|
||||||
{
|
{
|
||||||
int value = data.toInt();
|
int value = data.toInt();
|
||||||
|
|
@ -49,7 +49,7 @@ bool CSMFilter::TextNode::test(const CSMWorld::IdTableBase& table, int row, cons
|
||||||
if (value >= 0 && value < static_cast<int>(enums.size()))
|
if (value >= 0 && value < static_cast<int>(enums.size()))
|
||||||
string = QString::fromUtf8(enums[value].second.c_str());
|
string = QString::fromUtf8(enums[value].second.c_str());
|
||||||
}
|
}
|
||||||
else if (data.type() == QVariant::Bool)
|
else if (data.typeId() == QMetaType::Bool)
|
||||||
{
|
{
|
||||||
string = data.toBool() ? "true" : "false";
|
string = data.toBool() ? "true" : "false";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ bool CSMFilter::ValueNode::test(const CSMWorld::IdTableBase& table, int row, con
|
||||||
|
|
||||||
QVariant data = table.data(index);
|
QVariant data = table.data(index);
|
||||||
|
|
||||||
if (data.type() != QVariant::Double && data.type() != QVariant::Bool && data.type() != QVariant::Int
|
if (data.typeId() != QMetaType::Double && data.typeId() != QMetaType::Bool && data.typeId() != QMetaType::Int
|
||||||
&& data.type() != QVariant::UInt && data.type() != static_cast<QVariant::Type>(QMetaType::Float))
|
&& data.typeId() != QMetaType::UInt && data.typeId() != QMetaType::Float)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
double value = data.toDouble();
|
double value = data.toDouble();
|
||||||
|
|
|
||||||
|
|
@ -62,39 +62,31 @@ namespace CSMPrefs
|
||||||
{
|
{
|
||||||
QWidget* widget = static_cast<QWidget*>(watched);
|
QWidget* widget = static_cast<QWidget*>(watched);
|
||||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
unsigned int mod = (unsigned int)keyEvent->modifiers();
|
|
||||||
unsigned int key = (unsigned int)keyEvent->key();
|
|
||||||
|
|
||||||
if (!keyEvent->isAutoRepeat())
|
if (!keyEvent->isAutoRepeat())
|
||||||
return activate(widget, mod, key);
|
return activate(widget, keyEvent->keyCombination());
|
||||||
}
|
}
|
||||||
else if (event->type() == QEvent::KeyRelease)
|
else if (event->type() == QEvent::KeyRelease)
|
||||||
{
|
{
|
||||||
QWidget* widget = static_cast<QWidget*>(watched);
|
QWidget* widget = static_cast<QWidget*>(watched);
|
||||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
unsigned int mod = (unsigned int)keyEvent->modifiers();
|
|
||||||
unsigned int key = (unsigned int)keyEvent->key();
|
|
||||||
|
|
||||||
if (!keyEvent->isAutoRepeat())
|
if (!keyEvent->isAutoRepeat())
|
||||||
return deactivate(widget, mod, key);
|
return deactivate(widget, keyEvent->keyCombination());
|
||||||
}
|
}
|
||||||
else if (event->type() == QEvent::MouseButtonPress)
|
else if (event->type() == QEvent::MouseButtonPress)
|
||||||
{
|
{
|
||||||
QWidget* widget = static_cast<QWidget*>(watched);
|
QWidget* widget = static_cast<QWidget*>(watched);
|
||||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||||
unsigned int mod = (unsigned int)mouseEvent->modifiers();
|
|
||||||
unsigned int button = (unsigned int)mouseEvent->button();
|
|
||||||
|
|
||||||
return activate(widget, mod, button);
|
return activate(widget, QKeyCombination(mouseEvent->modifiers(), Qt::Key(mouseEvent->button())));
|
||||||
}
|
}
|
||||||
else if (event->type() == QEvent::MouseButtonRelease)
|
else if (event->type() == QEvent::MouseButtonRelease)
|
||||||
{
|
{
|
||||||
QWidget* widget = static_cast<QWidget*>(watched);
|
QWidget* widget = static_cast<QWidget*>(watched);
|
||||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||||
unsigned int mod = (unsigned int)mouseEvent->modifiers();
|
|
||||||
unsigned int button = (unsigned int)mouseEvent->button();
|
|
||||||
|
|
||||||
return deactivate(widget, mod, button);
|
return deactivate(widget, QKeyCombination(mouseEvent->modifiers(), Qt::Key(mouseEvent->button())));
|
||||||
}
|
}
|
||||||
else if (event->type() == QEvent::FocusOut)
|
else if (event->type() == QEvent::FocusOut)
|
||||||
{
|
{
|
||||||
|
|
@ -149,7 +141,7 @@ namespace CSMPrefs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button)
|
bool ShortcutEventHandler::activate(QWidget* widget, QKeyCombination keyCombination)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<MatchResult, Shortcut*>> potentials;
|
std::vector<std::pair<MatchResult, Shortcut*>> potentials;
|
||||||
bool used = false;
|
bool used = false;
|
||||||
|
|
@ -167,7 +159,7 @@ namespace CSMPrefs
|
||||||
if (!shortcut->isEnabled())
|
if (!shortcut->isEnabled())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (checkModifier(mod, button, shortcut, true))
|
if (checkModifier(keyCombination, shortcut, true))
|
||||||
used = true;
|
used = true;
|
||||||
|
|
||||||
if (shortcut->getActivationStatus() != Shortcut::AS_Inactive)
|
if (shortcut->getActivationStatus() != Shortcut::AS_Inactive)
|
||||||
|
|
@ -175,7 +167,8 @@ namespace CSMPrefs
|
||||||
|
|
||||||
int pos = shortcut->getPosition();
|
int pos = shortcut->getPosition();
|
||||||
int lastPos = shortcut->getLastPosition();
|
int lastPos = shortcut->getLastPosition();
|
||||||
MatchResult result = match(mod, button, shortcut->getSequence()[pos]);
|
MatchResult result = match(keyCombination.keyboardModifiers(), keyCombination.key(),
|
||||||
|
shortcut->getSequence()[pos].toCombined());
|
||||||
|
|
||||||
if (result == Matches_WithMod || result == Matches_NoMod)
|
if (result == Matches_WithMod || result == Matches_NoMod)
|
||||||
{
|
{
|
||||||
|
|
@ -220,10 +213,8 @@ namespace CSMPrefs
|
||||||
return used;
|
return used;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShortcutEventHandler::deactivate(QWidget* widget, unsigned int mod, unsigned int button)
|
bool ShortcutEventHandler::deactivate(QWidget* widget, QKeyCombination keyCombination)
|
||||||
{
|
{
|
||||||
const int KeyMask = 0x01FFFFFF;
|
|
||||||
|
|
||||||
bool used = false;
|
bool used = false;
|
||||||
|
|
||||||
while (widget)
|
while (widget)
|
||||||
|
|
@ -235,11 +226,11 @@ namespace CSMPrefs
|
||||||
{
|
{
|
||||||
Shortcut* shortcut = *it;
|
Shortcut* shortcut = *it;
|
||||||
|
|
||||||
if (checkModifier(mod, button, shortcut, false))
|
if (checkModifier(keyCombination, shortcut, false))
|
||||||
used = true;
|
used = true;
|
||||||
|
|
||||||
int pos = shortcut->getPosition();
|
int pos = shortcut->getPosition();
|
||||||
MatchResult result = match(0, button, shortcut->getSequence()[pos] & KeyMask);
|
MatchResult result = match(0, keyCombination.key(), shortcut->getSequence()[pos].key());
|
||||||
|
|
||||||
if (result != Matches_Not)
|
if (result != Matches_Not)
|
||||||
{
|
{
|
||||||
|
|
@ -268,13 +259,13 @@ namespace CSMPrefs
|
||||||
return used;
|
return used;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate)
|
bool ShortcutEventHandler::checkModifier(QKeyCombination keyCombination, Shortcut* shortcut, bool activate)
|
||||||
{
|
{
|
||||||
if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore
|
if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore
|
||||||
|| shortcut->getModifierStatus() == activate)
|
|| shortcut->getModifierStatus() == activate)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
MatchResult result = match(mod, button, shortcut->getModifier());
|
MatchResult result = match(keyCombination.keyboardModifiers(), keyCombination.key(), shortcut->getModifier());
|
||||||
bool used = false;
|
bool used = false;
|
||||||
|
|
||||||
if (result != Matches_Not)
|
if (result != Matches_Not)
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ namespace CSMPrefs
|
||||||
|
|
||||||
void updateParent(QWidget* widget);
|
void updateParent(QWidget* widget);
|
||||||
|
|
||||||
bool activate(QWidget* widget, unsigned int mod, unsigned int button);
|
bool activate(QWidget* widget, QKeyCombination keyCombination);
|
||||||
|
|
||||||
bool deactivate(QWidget* widget, unsigned int mod, unsigned int button);
|
bool deactivate(QWidget* widget, QKeyCombination keyCombination);
|
||||||
|
|
||||||
bool checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate);
|
bool checkModifier(QKeyCombination keyCombination, Shortcut* shortcut, bool activate);
|
||||||
|
|
||||||
MatchResult match(unsigned int mod, unsigned int button, unsigned int value);
|
MatchResult match(unsigned int mod, unsigned int button, unsigned int value);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,15 +115,12 @@ namespace CSMPrefs
|
||||||
|
|
||||||
std::string ShortcutManager::convertToString(const QKeySequence& sequence) const
|
std::string ShortcutManager::convertToString(const QKeySequence& sequence) const
|
||||||
{
|
{
|
||||||
const int MouseKeyMask = 0x01FFFFFF;
|
|
||||||
const int ModMask = 0x7E000000;
|
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
for (int i = 0; i < (int)sequence.count(); ++i)
|
for (int i = 0; i < sequence.count(); ++i)
|
||||||
{
|
{
|
||||||
int mods = sequence[i] & ModMask;
|
int mods = sequence[i].keyboardModifiers();
|
||||||
int key = sequence[i] & MouseKeyMask;
|
int key = sequence[i].key();
|
||||||
|
|
||||||
if (key)
|
if (key)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,6 @@ void CSMPrefs::State::declare()
|
||||||
.setTooltip("Minimum width of subviews.")
|
.setTooltip("Minimum width of subviews.")
|
||||||
.setRange(50, 10000);
|
.setRange(50, 10000);
|
||||||
declareEnum(mValues->mWindows.mMainwindowScrollbar, "Main Window Horizontal Scrollbar Mode");
|
declareEnum(mValues->mWindows.mMainwindowScrollbar, "Main Window Horizontal Scrollbar Mode");
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen")
|
|
||||||
.setTooltip(
|
|
||||||
"When \"Grow then Scroll\" option is selected, the window size grows to"
|
|
||||||
" the width of the virtual desktop. \nIf this option is selected the the window growth"
|
|
||||||
"is limited to the current screen.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
declareCategory("Records");
|
declareCategory("Records");
|
||||||
declareEnum(mValues->mRecords.mStatusFormat, "Modification Status Display Format");
|
declareEnum(mValues->mRecords.mStatusFormat, "Modification Status Display Format");
|
||||||
|
|
|
||||||
|
|
@ -507,8 +507,10 @@ namespace CSMPrefs
|
||||||
Settings::SettingValue<std::string> mOrbitRollRight{ mIndex, sName, "orbit-roll-right", "E" };
|
Settings::SettingValue<std::string> mOrbitRollRight{ mIndex, sName, "orbit-roll-right", "E" };
|
||||||
Settings::SettingValue<std::string> mOrbitSpeedMode{ mIndex, sName, "orbit-speed-mode", "" };
|
Settings::SettingValue<std::string> mOrbitSpeedMode{ mIndex, sName, "orbit-speed-mode", "" };
|
||||||
Settings::SettingValue<std::string> mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" };
|
Settings::SettingValue<std::string> mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" };
|
||||||
Settings::SettingValue<std::string> mScriptEditorComment{ mIndex, sName, "script-editor-comment", "" };
|
Settings::SettingValue<std::string> mScriptEditorComment{ mIndex, sName, "script-editor-comment",
|
||||||
Settings::SettingValue<std::string> mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment", "" };
|
"Ctrl+Slash" };
|
||||||
|
Settings::SettingValue<std::string> mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment",
|
||||||
|
"Ctrl+Shift+Question" };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModelsCategory : Settings::WithIndex
|
struct ModelsCategory : Settings::WithIndex
|
||||||
|
|
|
||||||
|
|
@ -659,11 +659,7 @@ void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string
|
||||||
//
|
//
|
||||||
mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only";
|
mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only";
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
updateWidth(windows["grow-limit"].isTrue(), minWidth);
|
|
||||||
#else
|
|
||||||
updateWidth(true, minWidth);
|
updateWidth(true, minWidth);
|
||||||
#endif
|
|
||||||
|
|
||||||
mSubViewWindow.addDockWidget(Qt::TopDockWidgetArea, view);
|
mSubViewWindow.addDockWidget(Qt::TopDockWidgetArea, view);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace CSVFilter
|
||||||
std::pair<std::string, FilterType> operator()(const QVariant& variantData)
|
std::pair<std::string, FilterType> operator()(const QVariant& variantData)
|
||||||
{
|
{
|
||||||
FilterType filterType = FilterType::String;
|
FilterType filterType = FilterType::String;
|
||||||
QMetaType::Type dataType = static_cast<QMetaType::Type>(variantData.type());
|
QMetaType::Type dataType = static_cast<QMetaType::Type>(variantData.typeId());
|
||||||
if (dataType == QMetaType::QString || dataType == QMetaType::Bool || dataType == QMetaType::Int)
|
if (dataType == QMetaType::QString || dataType == QMetaType::Bool || dataType == QMetaType::Int)
|
||||||
filterType = FilterType::String;
|
filterType = FilterType::String;
|
||||||
if (dataType == QMetaType::Int || dataType == QMetaType::Float)
|
if (dataType == QMetaType::Int || dataType == QMetaType::Float)
|
||||||
|
|
|
||||||
|
|
@ -1132,7 +1132,7 @@ void CSVRender::InstanceMode::dropEvent(QDropEvent* event)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WorldspaceHitResult hit
|
WorldspaceHitResult hit
|
||||||
= getWorldspaceWidget().mousePick(event->pos(), getWorldspaceWidget().getInteractionMask());
|
= getWorldspaceWidget().mousePick(event->position().toPoint(), getWorldspaceWidget().getInteractionMask());
|
||||||
|
|
||||||
std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos);
|
std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,6 @@ namespace CSVRender
|
||||||
, mLighting(nullptr)
|
, mLighting(nullptr)
|
||||||
, mHasDefaultAmbient(false)
|
, mHasDefaultAmbient(false)
|
||||||
, mIsExterior(true)
|
, mIsExterior(true)
|
||||||
, mPrevMouseX(0)
|
|
||||||
, mPrevMouseY(0)
|
|
||||||
, mCamPositionSet(false)
|
, mCamPositionSet(false)
|
||||||
{
|
{
|
||||||
mFreeCamControl = new FreeCameraController(this);
|
mFreeCamControl = new FreeCameraController(this);
|
||||||
|
|
@ -423,10 +421,10 @@ namespace CSVRender
|
||||||
|
|
||||||
void SceneWidget::mouseMoveEvent(QMouseEvent* event)
|
void SceneWidget::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY);
|
QPointF pos = event->position();
|
||||||
|
mCurrentCamControl->handleMouseMoveEvent(pos.x() - mPrevMouse.x(), pos.y() - mPrevMouse.y());
|
||||||
|
|
||||||
mPrevMouseX = event->x();
|
mPrevMouse = pos;
|
||||||
mPrevMouseY = event->y();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneWidget::wheelEvent(QWheelEvent* event)
|
void SceneWidget::wheelEvent(QWheelEvent* event)
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ namespace CSVRender
|
||||||
LightingNight mLightingNight;
|
LightingNight mLightingNight;
|
||||||
LightingBright mLightingBright;
|
LightingBright mLightingBright;
|
||||||
|
|
||||||
int mPrevMouseX, mPrevMouseY;
|
QPointF mPrevMouse;
|
||||||
|
|
||||||
/// Tells update that camera isn't set
|
/// Tells update that camera isn't set
|
||||||
bool mCamPositionSet;
|
bool mCamPositionSet;
|
||||||
|
|
|
||||||
|
|
@ -1661,7 +1661,7 @@ void CSVRender::TerrainShapeMode::dragMoveEvent(QDragMoveEvent* event) {}
|
||||||
|
|
||||||
void CSVRender::TerrainShapeMode::mouseMoveEvent(QMouseEvent* event)
|
void CSVRender::TerrainShapeMode::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask());
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->position().toPoint(), getInteractionMask());
|
||||||
if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
||||||
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
|
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
|
||||||
if (!hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
if (!hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
||||||
|
|
|
||||||
|
|
@ -724,7 +724,7 @@ void CSVRender::TerrainTextureMode::dragMoveEvent(QDragMoveEvent* event) {}
|
||||||
|
|
||||||
void CSVRender::TerrainTextureMode::mouseMoveEvent(QMouseEvent* event)
|
void CSVRender::TerrainTextureMode::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask());
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->position().toPoint(), getInteractionMask());
|
||||||
if (hit.hit && mBrushDraw)
|
if (hit.hit && mBrushDraw)
|
||||||
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
|
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
|
||||||
if (!hit.hit && mBrushDraw)
|
if (!hit.hit && mBrushDraw)
|
||||||
|
|
|
||||||
|
|
@ -687,11 +687,12 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
|
||||||
|
|
||||||
if (mDragging)
|
if (mDragging)
|
||||||
{
|
{
|
||||||
int diffX = event->x() - mDragX;
|
QPoint pos = event->position().toPoint();
|
||||||
int diffY = (height() - event->y()) - mDragY;
|
int diffX = pos.x() - mDragX;
|
||||||
|
int diffY = (height() - pos.y()) - mDragY;
|
||||||
|
|
||||||
mDragX = event->x();
|
mDragX = pos.x();
|
||||||
mDragY = height() - event->y();
|
mDragY = height() - pos.y();
|
||||||
|
|
||||||
double factor = mDragFactor;
|
double factor = mDragFactor;
|
||||||
|
|
||||||
|
|
@ -700,32 +701,32 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
|
||||||
|
|
||||||
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
||||||
|
|
||||||
editMode.drag(event->pos(), diffX, diffY, factor);
|
editMode.drag(event->position().toPoint(), diffX, diffY, factor);
|
||||||
}
|
}
|
||||||
else if (mDragMode != InteractionType_None)
|
else if (mDragMode != InteractionType_None)
|
||||||
{
|
{
|
||||||
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
||||||
|
|
||||||
if (mDragMode == InteractionType_PrimaryEdit)
|
if (mDragMode == InteractionType_PrimaryEdit)
|
||||||
mDragging = editMode.primaryEditStartDrag(event->pos());
|
mDragging = editMode.primaryEditStartDrag(event->position().toPoint());
|
||||||
else if (mDragMode == InteractionType_SecondaryEdit)
|
else if (mDragMode == InteractionType_SecondaryEdit)
|
||||||
mDragging = editMode.secondaryEditStartDrag(event->pos());
|
mDragging = editMode.secondaryEditStartDrag(event->position().toPoint());
|
||||||
else if (mDragMode == InteractionType_PrimarySelect)
|
else if (mDragMode == InteractionType_PrimarySelect)
|
||||||
mDragging = editMode.primarySelectStartDrag(event->pos());
|
mDragging = editMode.primarySelectStartDrag(event->position().toPoint());
|
||||||
else if (mDragMode == InteractionType_SecondarySelect)
|
else if (mDragMode == InteractionType_SecondarySelect)
|
||||||
mDragging = editMode.secondarySelectStartDrag(event->pos());
|
mDragging = editMode.secondarySelectStartDrag(event->position().toPoint());
|
||||||
|
|
||||||
if (mDragging)
|
if (mDragging)
|
||||||
{
|
{
|
||||||
mDragX = event->localPos().x();
|
mDragX = event->position().x();
|
||||||
mDragY = height() - event->localPos().y();
|
mDragY = height() - event->position().y();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (event->globalPos() != mToolTipPos)
|
if (event->globalPosition().toPoint() != mToolTipPos)
|
||||||
{
|
{
|
||||||
mToolTipPos = event->globalPos();
|
mToolTipPos = event->globalPosition().toPoint();
|
||||||
|
|
||||||
if (mShowToolTips)
|
if (mShowToolTips)
|
||||||
{
|
{
|
||||||
|
|
@ -734,7 +735,7 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const QPointF& pos = event->localPos();
|
QPoint pos = event->position().toPoint();
|
||||||
handleMarkerHighlight(pos.x(), pos.y());
|
handleMarkerHighlight(pos.x(), pos.y());
|
||||||
SceneWidget::mouseMoveEvent(event);
|
SceneWidget::mouseMoveEvent(event);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -260,8 +260,6 @@ namespace CSVRender
|
||||||
|
|
||||||
void settingChanged(const CSMPrefs::Setting* setting) override;
|
void settingChanged(const CSMPrefs::Setting* setting) override;
|
||||||
|
|
||||||
bool getSpeedMode();
|
|
||||||
|
|
||||||
void cycleNavigationMode();
|
void cycleNavigationMode();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ void CSVTools::ReportTable::contextMenuEvent(QContextMenuEvent* event)
|
||||||
void CSVTools::ReportTable::mouseMoveEvent(QMouseEvent* event)
|
void CSVTools::ReportTable::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
if (event->buttons() & Qt::LeftButton)
|
if (event->buttons() & Qt::LeftButton)
|
||||||
startDragFromTable(*this, indexAt(event->pos()));
|
startDragFromTable(*this, indexAt(event->position().toPoint()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVTools::ReportTable::mouseDoubleClickEvent(QMouseEvent* event)
|
void CSVTools::ReportTable::mouseDoubleClickEvent(QMouseEvent* event)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent* event)
|
||||||
|
|
||||||
// If the mouse is pressed above the pop-up parent,
|
// If the mouse is pressed above the pop-up parent,
|
||||||
// the pop-up will be hidden and the pressed signal won't be repeated for the parent
|
// the pop-up will be hidden and the pressed signal won't be repeated for the parent
|
||||||
if (buttonRect.contains(event->globalPos()) || buttonRect.contains(event->pos()))
|
if (buttonRect.contains(event->globalPosition().toPoint()) || buttonRect.contains(event->position().toPoint()))
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_NoMouseReplay);
|
setAttribute(Qt::WA_NoMouseReplay);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,8 @@ int CSVWidget::CompleterPopup::sizeHintForRow(int row) const
|
||||||
|
|
||||||
ensurePolished();
|
ensurePolished();
|
||||||
QModelIndex index = model()->index(row, modelColumn());
|
QModelIndex index = model()->index(row, modelColumn());
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
QStyleOptionViewItem option;
|
QStyleOptionViewItem option;
|
||||||
initViewItemOption(&option);
|
initViewItemOption(&option);
|
||||||
#else
|
QAbstractItemDelegate* delegate = itemDelegateForIndex(index);
|
||||||
QStyleOptionViewItem option = viewOptions();
|
|
||||||
#endif
|
|
||||||
QAbstractItemDelegate* delegate = itemDelegate(index);
|
|
||||||
return delegate->sizeHint(option, index).height();
|
return delegate->sizeHint(option, index).height();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData(QWidget* editor, const QMod
|
||||||
CSMWorld::Columns::ColumnId columnId
|
CSMWorld::Columns::ColumnId columnId
|
||||||
= static_cast<CSMWorld::Columns::ColumnId>(mTable->getColumnId(index.column()));
|
= static_cast<CSMWorld::Columns::ColumnId>(mTable->getColumnId(index.column()));
|
||||||
|
|
||||||
if (QVariant::String == v.type())
|
if (QMetaType::QString == v.typeId())
|
||||||
{
|
{
|
||||||
label->setText(v.toString());
|
label->setText(v.toString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent* event)
|
||||||
|
|
||||||
void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent* event)
|
void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent* event)
|
||||||
{
|
{
|
||||||
QModelIndex index = indexAt(event->pos());
|
QModelIndex index = indexAt(event->position().toPoint());
|
||||||
if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))
|
if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))
|
||||||
|| CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index))
|
|| CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index))
|
||||||
|| CSVWorld::DragDropUtils::isTopicOrJournal(*event, getIndexDisplayType(index)))
|
|| CSVWorld::DragDropUtils::isTopicOrJournal(*event, getIndexDisplayType(index)))
|
||||||
|
|
@ -71,7 +71,7 @@ void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent* event)
|
||||||
|
|
||||||
void CSVWorld::DragRecordTable::dropEvent(QDropEvent* event)
|
void CSVWorld::DragRecordTable::dropEvent(QDropEvent* event)
|
||||||
{
|
{
|
||||||
QModelIndex index = indexAt(event->pos());
|
QModelIndex index = indexAt(event->position().toPoint());
|
||||||
CSMWorld::ColumnBase::Display display = getIndexDisplayType(index);
|
CSMWorld::ColumnBase::Display display = getIndexDisplayType(index);
|
||||||
if (CSVWorld::DragDropUtils::canAcceptData(*event, display))
|
if (CSVWorld::DragDropUtils::canAcceptData(*event, display))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ void CSVWorld::RegionMap::viewInTable()
|
||||||
|
|
||||||
void CSVWorld::RegionMap::mouseMoveEvent(QMouseEvent* event)
|
void CSVWorld::RegionMap::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
startDragFromTable(*this, indexAt(event->pos()));
|
startDragFromTable(*this, indexAt(event->position().toPoint()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CSMWorld::UniversalId> CSVWorld::RegionMap::getDraggedRecords() const
|
std::vector<CSMWorld::UniversalId> CSVWorld::RegionMap::getDraggedRecords() const
|
||||||
|
|
@ -376,7 +376,7 @@ void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event)
|
||||||
|
|
||||||
void CSVWorld::RegionMap::dropEvent(QDropEvent* event)
|
void CSVWorld::RegionMap::dropEvent(QDropEvent* event)
|
||||||
{
|
{
|
||||||
QModelIndex index = indexAt(event->pos());
|
QModelIndex index = indexAt(event->position().toPoint());
|
||||||
|
|
||||||
bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern);
|
bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern);
|
||||||
if (!index.isValid() || !exists)
|
if (!index.isValid() || !exists)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,24 @@
|
||||||
#include "../../model/world/tablemimedata.hpp"
|
#include "../../model/world/tablemimedata.hpp"
|
||||||
#include "../../model/world/universalid.hpp"
|
#include "../../model/world/universalid.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void prependToEachLine(QTextCursor begin, const QString& text)
|
||||||
|
{
|
||||||
|
QTextCursor end = begin;
|
||||||
|
begin.setPosition(begin.selectionStart());
|
||||||
|
begin.movePosition(QTextCursor::StartOfLine);
|
||||||
|
end.setPosition(end.selectionEnd());
|
||||||
|
end.movePosition(QTextCursor::EndOfLine);
|
||||||
|
begin.beginEditBlock();
|
||||||
|
for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right))
|
||||||
|
{
|
||||||
|
begin.insertText(text);
|
||||||
|
}
|
||||||
|
begin.endEditBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CSVWorld::ScriptEdit::ChangeLock::ChangeLock(ScriptEdit& edit)
|
CSVWorld::ScriptEdit::ChangeLock::ChangeLock(ScriptEdit& edit)
|
||||||
: mEdit(edit)
|
: mEdit(edit)
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +64,55 @@ bool CSVWorld::ScriptEdit::event(QEvent* event)
|
||||||
return QPlainTextEdit::event(event);
|
return QPlainTextEdit::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVWorld::ScriptEdit::keyPressEvent(QKeyEvent* event)
|
||||||
|
{
|
||||||
|
if (event->key() == Qt::Key_Backtab)
|
||||||
|
{
|
||||||
|
QTextCursor cursor = textCursor();
|
||||||
|
QTextCursor end = cursor;
|
||||||
|
cursor.setPosition(cursor.selectionStart());
|
||||||
|
cursor.movePosition(QTextCursor::StartOfLine);
|
||||||
|
end.setPosition(end.selectionEnd());
|
||||||
|
end.movePosition(QTextCursor::EndOfLine);
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
for (; cursor < end; cursor.movePosition(QTextCursor::EndOfLine), cursor.movePosition(QTextCursor::Right))
|
||||||
|
{
|
||||||
|
cursor.select(QTextCursor::LineUnderCursor);
|
||||||
|
QString line = cursor.selectedText();
|
||||||
|
|
||||||
|
if (line.isEmpty())
|
||||||
|
continue;
|
||||||
|
qsizetype index = 0;
|
||||||
|
if (line[0] == '\t')
|
||||||
|
index = 1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove up to a tab worth of spaces instead
|
||||||
|
while (line[index].isSpace() && index < mTabCharCount && line[index] != '\t')
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != 0)
|
||||||
|
{
|
||||||
|
line.remove(0, index);
|
||||||
|
cursor.insertText(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (event->key() == Qt::Key_Tab)
|
||||||
|
{
|
||||||
|
QTextCursor cursor = textCursor();
|
||||||
|
if (cursor.hasSelection())
|
||||||
|
{
|
||||||
|
prependToEachLine(cursor, "\t");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPlainTextEdit::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
CSVWorld::ScriptEdit::ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent)
|
CSVWorld::ScriptEdit::ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent)
|
||||||
: QPlainTextEdit(parent)
|
: QPlainTextEdit(parent)
|
||||||
, mChangeLocked(0)
|
, mChangeLocked(0)
|
||||||
|
|
@ -136,7 +203,7 @@ void CSVWorld::ScriptEdit::dragEnterEvent(QDragEnterEvent* event)
|
||||||
QPlainTextEdit::dragEnterEvent(event);
|
QPlainTextEdit::dragEnterEvent(event);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setTextCursor(cursorForPosition(event->pos()));
|
setTextCursor(cursorForPosition(event->position().toPoint()));
|
||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +215,7 @@ void CSVWorld::ScriptEdit::dragMoveEvent(QDragMoveEvent* event)
|
||||||
QPlainTextEdit::dragMoveEvent(event);
|
QPlainTextEdit::dragMoveEvent(event);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setTextCursor(cursorForPosition(event->pos()));
|
setTextCursor(cursorForPosition(event->position().toPoint()));
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +229,7 @@ void CSVWorld::ScriptEdit::dropEvent(QDropEvent* event)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextCursor(cursorForPosition(event->pos()));
|
setTextCursor(cursorForPosition(event->position().toPoint()));
|
||||||
|
|
||||||
if (mime->fromDocument(mDocument))
|
if (mime->fromDocument(mDocument))
|
||||||
{
|
{
|
||||||
|
|
@ -316,22 +383,7 @@ void CSVWorld::ScriptEdit::markOccurrences()
|
||||||
|
|
||||||
void CSVWorld::ScriptEdit::commentSelection()
|
void CSVWorld::ScriptEdit::commentSelection()
|
||||||
{
|
{
|
||||||
QTextCursor begin = textCursor();
|
prependToEachLine(textCursor(), ";");
|
||||||
QTextCursor end = begin;
|
|
||||||
begin.setPosition(begin.selectionStart());
|
|
||||||
begin.movePosition(QTextCursor::StartOfLine);
|
|
||||||
|
|
||||||
end.setPosition(end.selectionEnd());
|
|
||||||
end.movePosition(QTextCursor::EndOfLine);
|
|
||||||
|
|
||||||
begin.beginEditBlock();
|
|
||||||
|
|
||||||
for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right))
|
|
||||||
{
|
|
||||||
begin.insertText(";");
|
|
||||||
}
|
|
||||||
|
|
||||||
begin.endEditBlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWorld::ScriptEdit::uncommentSelection()
|
void CSVWorld::ScriptEdit::uncommentSelection()
|
||||||
|
|
@ -345,17 +397,16 @@ void CSVWorld::ScriptEdit::uncommentSelection()
|
||||||
end.movePosition(QTextCursor::EndOfLine);
|
end.movePosition(QTextCursor::EndOfLine);
|
||||||
|
|
||||||
begin.beginEditBlock();
|
begin.beginEditBlock();
|
||||||
|
|
||||||
for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right))
|
for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right))
|
||||||
{
|
{
|
||||||
begin.select(QTextCursor::LineUnderCursor);
|
begin.select(QTextCursor::LineUnderCursor);
|
||||||
QString line = begin.selectedText();
|
QString line = begin.selectedText();
|
||||||
|
|
||||||
if (line.size() == 0)
|
if (line.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// get first nonspace character in line
|
// get first nonspace character in line
|
||||||
int index;
|
qsizetype index;
|
||||||
for (index = 0; index != line.size(); ++index)
|
for (index = 0; index != line.size(); ++index)
|
||||||
{
|
{
|
||||||
if (!line[index].isSpace())
|
if (!line[index].isSpace())
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ namespace CSVWorld
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool event(QEvent* event) override;
|
bool event(QEvent* event) override;
|
||||||
|
void keyPressEvent(QKeyEvent* e) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent);
|
ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent);
|
||||||
|
|
|
||||||
|
|
@ -592,7 +592,7 @@ void CSVWorld::Table::moveRecords(QDropEvent* event)
|
||||||
if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
|
if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QModelIndex targedIndex = indexAt(event->pos());
|
QModelIndex targedIndex = indexAt(event->position().toPoint());
|
||||||
|
|
||||||
QModelIndexList selectedRows = selectionModel()->selectedRows();
|
QModelIndexList selectedRows = selectionModel()->selectedRows();
|
||||||
int targetRowRaw = targedIndex.row();
|
int targetRowRaw = targedIndex.row();
|
||||||
|
|
@ -872,7 +872,7 @@ void CSVWorld::Table::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
if (event->buttons() & Qt::LeftButton)
|
if (event->buttons() & Qt::LeftButton)
|
||||||
{
|
{
|
||||||
startDragFromTable(*this, indexAt(event->pos()));
|
startDragFromTable(*this, indexAt(event->position().toPoint()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ namespace CSVWorld
|
||||||
auto& clickEvent = static_cast<QMouseEvent&>(*event);
|
auto& clickEvent = static_cast<QMouseEvent&>(*event);
|
||||||
if ((clickEvent.button() == Qt::MiddleButton))
|
if ((clickEvent.button() == Qt::MiddleButton))
|
||||||
{
|
{
|
||||||
const auto& index = table.indexAt(clickEvent.pos());
|
const auto& index = table.indexAt(clickEvent.position().toPoint());
|
||||||
table.setColumnHidden(index.column(), true);
|
table.setColumnHidden(index.column(), true);
|
||||||
clickEvent.accept();
|
clickEvent.accept();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ QWidget* CSVWorld::CommandDelegate::createEditor(
|
||||||
// TODO: Find a better solution?
|
// TODO: Find a better solution?
|
||||||
if (display == CSMWorld::ColumnBase::Display_Boolean)
|
if (display == CSMWorld::ColumnBase::Display_Boolean)
|
||||||
{
|
{
|
||||||
return QItemEditorFactory::defaultFactory()->createEditor(QVariant::Bool, parent);
|
return QItemEditorFactory::defaultFactory()->createEditor(QMetaType::Bool, parent);
|
||||||
}
|
}
|
||||||
// For tables the pop-up of the color editor should appear immediately after the editor creation
|
// For tables the pop-up of the color editor should appear immediately after the editor creation
|
||||||
// (the third parameter of ColorEditor's constructor)
|
// (the third parameter of ColorEditor's constructor)
|
||||||
|
|
@ -362,11 +362,7 @@ void CSVWorld::CommandDelegate::setEditorData(QWidget* editor, const QModelIndex
|
||||||
if (!n.isEmpty())
|
if (!n.isEmpty())
|
||||||
{
|
{
|
||||||
if (!variant.isValid())
|
if (!variant.isValid())
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
variant = QVariant(editor->property(n).metaType(), (const void*)nullptr);
|
variant = QVariant(editor->property(n).metaType(), (const void*)nullptr);
|
||||||
#else
|
|
||||||
variant = QVariant(editor->property(n).userType(), (const void*)nullptr);
|
|
||||||
#endif
|
|
||||||
editor->setProperty(n, variant);
|
editor->setProperty(n, variant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <SDL_events.h>
|
#include <SDL_events.h>
|
||||||
|
|
||||||
#include "../mwgui/mode.hpp"
|
#include "../mwgui/mode.hpp"
|
||||||
|
#include "../mwmechanics/damagesourcetype.hpp"
|
||||||
#include "../mwrender/animationpriority.hpp"
|
#include "../mwrender/animationpriority.hpp"
|
||||||
#include <components/sdlutil/events.hpp>
|
#include <components/sdlutil/events.hpp>
|
||||||
|
|
||||||
|
|
@ -39,6 +40,11 @@ namespace LuaUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Vec3f;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWBase
|
namespace MWBase
|
||||||
{
|
{
|
||||||
// \brief LuaManager is the central interface through which the engine invokes lua scripts.
|
// \brief LuaManager is the central interface through which the engine invokes lua scripts.
|
||||||
|
|
@ -71,6 +77,10 @@ namespace MWBase
|
||||||
= 0;
|
= 0;
|
||||||
virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0;
|
virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0;
|
||||||
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
|
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
|
||||||
|
virtual void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,
|
||||||
|
const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth,
|
||||||
|
const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType)
|
||||||
|
= 0;
|
||||||
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
|
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
|
||||||
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
|
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
|
||||||
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
|
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../mwmechanics/greetingstate.hpp"
|
|
||||||
#include "../mwrender/animationpriority.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
namespace osg
|
namespace osg
|
||||||
|
|
@ -27,6 +24,11 @@ namespace ESM
|
||||||
class ESMWriter;
|
class ESMWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
enum class GreetingState;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
|
|
||||||
|
|
@ -526,9 +526,6 @@ namespace MWBase
|
||||||
/// Spawn a random creature from a levelled list next to the player
|
/// Spawn a random creature from a levelled list next to the player
|
||||||
virtual void spawnRandomCreature(const ESM::RefId& creatureList) = 0;
|
virtual void spawnRandomCreature(const ESM::RefId& creatureList) = 0;
|
||||||
|
|
||||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
|
||||||
virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0;
|
|
||||||
|
|
||||||
virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
||||||
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true)
|
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true)
|
||||||
= 0;
|
= 0;
|
||||||
|
|
@ -589,8 +586,7 @@ namespace MWBase
|
||||||
virtual bool hasCollisionWithDoor(
|
virtual bool hasCollisionWithDoor(
|
||||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
||||||
|
|
||||||
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
virtual bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const = 0;
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
|
|
||||||
|
|
||||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
#include "../mwlua/localscripts.hpp"
|
||||||
|
|
||||||
#include "../mwworld/actionequip.hpp"
|
#include "../mwworld/actionequip.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
|
@ -109,8 +111,23 @@ namespace MWClass
|
||||||
return std::make_pair(slots_, false);
|
return std::make_pair(slots_, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::RefId Armor::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
|
ESM::RefId Armor::getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
|
// We don't actually need an actor as such. We just need an object that has
|
||||||
|
// lua scripts and the Combat interface.
|
||||||
|
if (useLuaInterfaceIfAvailable)
|
||||||
|
{
|
||||||
|
// In this interface call, both objects are effectively const, so stripping Const from the ConstPtr is fine.
|
||||||
|
MWWorld::Ptr mutablePtr(
|
||||||
|
const_cast<MWWorld::LiveCellRefBase*>(ptr.mRef), const_cast<MWWorld::CellStore*>(ptr.mCell));
|
||||||
|
auto res = MWLua::LocalScripts::callPlayerInterface<std::string>(
|
||||||
|
"Combat", "getArmorSkill", MWLua::LObject(mutablePtr));
|
||||||
|
if (res)
|
||||||
|
return ESM::RefId::deserializeText(res.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to the old engine implementation when actors don't have their scripts attached yet.
|
||||||
|
|
||||||
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
|
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
|
||||||
|
|
||||||
std::string_view typeGmst;
|
std::string_view typeGmst;
|
||||||
|
|
@ -175,7 +192,7 @@ namespace MWClass
|
||||||
|
|
||||||
const ESM::RefId& Armor::getUpSoundId(const MWWorld::ConstPtr& ptr) const
|
const ESM::RefId& Armor::getUpSoundId(const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const ESM::RefId es = getEquipmentSkill(ptr);
|
const ESM::RefId es = getEquipmentSkill(ptr, false);
|
||||||
static const ESM::RefId lightUp = ESM::RefId::stringRefId("Item Armor Light Up");
|
static const ESM::RefId lightUp = ESM::RefId::stringRefId("Item Armor Light Up");
|
||||||
static const ESM::RefId mediumUp = ESM::RefId::stringRefId("Item Armor Medium Up");
|
static const ESM::RefId mediumUp = ESM::RefId::stringRefId("Item Armor Medium Up");
|
||||||
static const ESM::RefId heavyUp = ESM::RefId::stringRefId("Item Armor Heavy Up");
|
static const ESM::RefId heavyUp = ESM::RefId::stringRefId("Item Armor Heavy Up");
|
||||||
|
|
@ -190,7 +207,7 @@ namespace MWClass
|
||||||
|
|
||||||
const ESM::RefId& Armor::getDownSoundId(const MWWorld::ConstPtr& ptr) const
|
const ESM::RefId& Armor::getDownSoundId(const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const ESM::RefId es = getEquipmentSkill(ptr);
|
const ESM::RefId es = getEquipmentSkill(ptr, false);
|
||||||
static const ESM::RefId lightDown = ESM::RefId::stringRefId("Item Armor Light Down");
|
static const ESM::RefId lightDown = ESM::RefId::stringRefId("Item Armor Light Down");
|
||||||
static const ESM::RefId mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down");
|
static const ESM::RefId mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down");
|
||||||
static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down");
|
static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down");
|
||||||
|
|
@ -221,24 +238,29 @@ namespace MWClass
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
// get armor type string (light/medium/heavy)
|
// get armor type string (light/medium/heavy)
|
||||||
std::string_view typeText;
|
std::string typeText;
|
||||||
if (ref->mBase->mData.mWeight == 0)
|
if (ref->mBase->mData.mWeight == 0)
|
||||||
{
|
{
|
||||||
// no type
|
// no type
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ESM::RefId armorType = getEquipmentSkill(ptr);
|
const ESM::RefId armorType = getEquipmentSkill(ptr, true);
|
||||||
if (armorType == ESM::Skill::LightArmor)
|
if (armorType == ESM::Skill::LightArmor)
|
||||||
typeText = "#{sLight}";
|
typeText = "#{sLight}";
|
||||||
else if (armorType == ESM::Skill::MediumArmor)
|
else if (armorType == ESM::Skill::MediumArmor)
|
||||||
typeText = "#{sMedium}";
|
typeText = "#{sMedium}";
|
||||||
else
|
else if (armorType == ESM::Skill::HeavyArmor)
|
||||||
typeText = "#{sHeavy}";
|
typeText = "#{sHeavy}";
|
||||||
|
// For other skills, just subtitute the skill name
|
||||||
|
// Normally you would never see this case, but modding allows getEquipmentSkill() to return any skill.
|
||||||
|
else
|
||||||
|
typeText = "#{sSkill" + armorType.toString() + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
text += "\n#{sArmorRating}: "
|
text += "\n#{sArmorRating}: "
|
||||||
+ MWGui::ToolTips::toString(static_cast<int>(getEffectiveArmorRating(ptr, MWMechanics::getPlayer())));
|
+ MWGui::ToolTips::toString(
|
||||||
|
static_cast<int>(getSkillAdjustedArmorRating(ptr, MWMechanics::getPlayer(), true)));
|
||||||
|
|
||||||
int remainingHealth = getItemHealth(ptr);
|
int remainingHealth = getItemHealth(ptr);
|
||||||
text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/"
|
text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/"
|
||||||
|
|
@ -289,11 +311,25 @@ namespace MWClass
|
||||||
return record->mId;
|
return record->mId;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Armor::getEffectiveArmorRating(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& actor) const
|
float Armor::getSkillAdjustedArmorRating(
|
||||||
|
const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& actor, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
|
if (useLuaInterfaceIfAvailable && actor == MWMechanics::getPlayer())
|
||||||
|
{
|
||||||
|
// In this interface call, both objects are effectively const, so stripping Const from the ConstPtr is fine.
|
||||||
|
MWWorld::Ptr mutablePtr(
|
||||||
|
const_cast<MWWorld::LiveCellRefBase*>(ptr.mRef), const_cast<MWWorld::CellStore*>(ptr.mCell));
|
||||||
|
auto res = MWLua::LocalScripts::callPlayerInterface<float>(
|
||||||
|
"Combat", "getSkillAdjustedArmorRating", MWLua::LObject(mutablePtr), MWLua::LObject(actor));
|
||||||
|
if (res)
|
||||||
|
return res.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to the old engine implementation when actors don't have their scripts attached yet.
|
||||||
|
|
||||||
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
|
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
|
||||||
|
|
||||||
const ESM::RefId armorSkillType = getEquipmentSkill(ptr);
|
const ESM::RefId armorSkillType = getEquipmentSkill(ptr, useLuaInterfaceIfAvailable);
|
||||||
float armorSkill = actor.getClass().getSkill(actor, armorSkillType);
|
float armorSkill = actor.getClass().getSkill(actor, armorSkillType);
|
||||||
|
|
||||||
int iBaseArmorSkill = MWBase::Environment::get()
|
int iBaseArmorSkill = MWBase::Environment::get()
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace MWClass
|
||||||
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
||||||
/// stay stacked when equipped?
|
/// stay stacked when equipped?
|
||||||
|
|
||||||
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
|
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const override;
|
||||||
|
|
||||||
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
|
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
|
||||||
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
|
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
|
||||||
|
|
@ -81,7 +81,8 @@ namespace MWClass
|
||||||
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;
|
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;
|
||||||
|
|
||||||
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
||||||
float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const override;
|
float getSkillAdjustedArmorRating(
|
||||||
|
const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor, bool useLuaInterfaceIfAvailable) const override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ namespace MWClass
|
||||||
return std::make_pair(slots_, false);
|
return std::make_pair(slots_, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::RefId Clothing::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
|
ESM::RefId Clothing::getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>();
|
const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace MWClass
|
||||||
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
||||||
/// stay stacked when equipped?
|
/// stay stacked when equipped?
|
||||||
|
|
||||||
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
|
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const override;
|
||||||
|
|
||||||
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
|
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
|
||||||
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
|
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,14 @@
|
||||||
#include "../mwmechanics/setbaseaisetting.hpp"
|
#include "../mwmechanics/setbaseaisetting.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/luamanager.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwlua/localscripts.hpp"
|
||||||
|
|
||||||
#include "../mwworld/actionopen.hpp"
|
#include "../mwworld/actionopen.hpp"
|
||||||
#include "../mwworld/actiontalk.hpp"
|
#include "../mwworld/actiontalk.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
@ -283,8 +286,8 @@ namespace MWClass
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
victim.getClass().onHit(
|
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
|
||||||
victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
|
0.0f, false, hitPosition, false, MWMechanics::DamageSourceType::Melee);
|
||||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -342,12 +345,12 @@ namespace MWClass
|
||||||
|
|
||||||
MWMechanics::diseaseContact(victim, ptr);
|
MWMechanics::diseaseContact(victim, ptr);
|
||||||
|
|
||||||
victim.getClass().onHit(
|
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
|
||||||
victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
damage, healthdmg, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
void Creature::onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages,
|
||||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
const MWWorld::Ptr& object, const MWWorld::Ptr& attacker, bool successful,
|
||||||
const MWMechanics::DamageSourceType sourceType) const
|
const MWMechanics::DamageSourceType sourceType) const
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
|
|
@ -397,19 +400,44 @@ namespace MWClass
|
||||||
if (!successful)
|
if (!successful)
|
||||||
{
|
{
|
||||||
// Missed
|
// Missed
|
||||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(
|
|
||||||
ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
stats.setLastHitObject(object.getCellRef().getRefId());
|
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (damage < 0.001f)
|
bool hasDamage = false;
|
||||||
damage = 0;
|
bool hasHealthDamage = false;
|
||||||
|
float healthDamage = 0.f;
|
||||||
|
for (auto& [stat, damage] : damages)
|
||||||
|
{
|
||||||
|
if (damage < 0.001f)
|
||||||
|
continue;
|
||||||
|
hasDamage = true;
|
||||||
|
|
||||||
if (damage > 0.f)
|
if (stat == "health")
|
||||||
|
{
|
||||||
|
hasHealthDamage = true;
|
||||||
|
healthDamage = damage;
|
||||||
|
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
||||||
|
health.setCurrent(health.getCurrent() - damage);
|
||||||
|
stats.setHealth(health);
|
||||||
|
}
|
||||||
|
else if (stat == "fatigue")
|
||||||
|
{
|
||||||
|
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||||
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||||
|
stats.setFatigue(fatigue);
|
||||||
|
}
|
||||||
|
else if (stat == "magicka")
|
||||||
|
{
|
||||||
|
MWMechanics::DynamicStat<float> magicka(getCreatureStats(ptr).getMagicka());
|
||||||
|
magicka.setCurrent(magicka.getCurrent() - damage);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasDamage)
|
||||||
{
|
{
|
||||||
if (!attacker.isEmpty())
|
if (!attacker.isEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -420,35 +448,11 @@ namespace MWClass
|
||||||
* getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
* getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
||||||
+ getGmst().iKnockDownOddsBase->mValue.getInteger();
|
+ getGmst().iKnockDownOddsBase->mValue.getInteger();
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
if (hasHealthDamage && agilityTerm <= healthDamage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||||
stats.setKnockedDown(true);
|
stats.setKnockedDown(true);
|
||||||
else
|
else
|
||||||
stats.setHitRecovery(true); // Is this supposed to always occur?
|
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ishealth)
|
|
||||||
{
|
|
||||||
damage *= damage / (damage + getArmorRating(ptr));
|
|
||||||
damage = std::max(1.f, damage);
|
|
||||||
if (!attacker.isEmpty())
|
|
||||||
{
|
|
||||||
damage = scaleDamage(damage, attacker, ptr);
|
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(
|
|
||||||
ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
|
||||||
|
|
||||||
MWMechanics::DynamicStat<float> health(stats.getHealth());
|
|
||||||
health.setCurrent(health.getCurrent() - damage);
|
|
||||||
stats.setHealth(health);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
|
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
|
||||||
stats.setFatigue(fatigue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +595,7 @@ namespace MWClass
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Creature::getArmorRating(const MWWorld::Ptr& ptr) const
|
float Creature::getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
// Equipment armor rating is deliberately ignored.
|
// Equipment armor rating is deliberately ignored.
|
||||||
return getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude();
|
return getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude();
|
||||||
|
|
@ -764,11 +768,6 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Creature::getBloodTexture(const MWWorld::ConstPtr& ptr) const
|
|
||||||
{
|
|
||||||
return ptr.get<ESM::Creature>()->mBase->mBloodType;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Creature::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
|
void Creature::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
|
||||||
{
|
{
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ namespace MWClass
|
||||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
||||||
const osg::Vec3f& hitPosition, bool success) const override;
|
const osg::Vec3f& hitPosition, bool success) const override;
|
||||||
|
|
||||||
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
void onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
|
||||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
const MWWorld::Ptr& attacker, bool successful,
|
||||||
const MWMechanics::DamageSourceType sourceType) const override;
|
const MWMechanics::DamageSourceType sourceType) const override;
|
||||||
|
|
||||||
std::unique_ptr<MWWorld::Action> activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override;
|
std::unique_ptr<MWWorld::Action> activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override;
|
||||||
|
|
@ -88,7 +88,7 @@ namespace MWClass
|
||||||
///< Return total weight that fits into the object. Throws an exception, if the object can't
|
///< Return total weight that fits into the object. Throws an exception, if the object can't
|
||||||
/// hold other objects.
|
/// hold other objects.
|
||||||
|
|
||||||
float getArmorRating(const MWWorld::Ptr& ptr) const override;
|
float getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable) const override;
|
||||||
///< @return combined armor rating of this actor
|
///< @return combined armor rating of this actor
|
||||||
|
|
||||||
bool isEssential(const MWWorld::ConstPtr& ptr) const override;
|
bool isEssential(const MWWorld::ConstPtr& ptr) const override;
|
||||||
|
|
@ -118,9 +118,6 @@ namespace MWClass
|
||||||
|
|
||||||
float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
|
float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
|
||||||
|
|
||||||
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
|
|
||||||
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;
|
|
||||||
|
|
||||||
void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override;
|
void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override;
|
||||||
///< Read additional state from \a state into \a ptr.
|
///< Read additional state from \a state into \a ptr.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwlua/localscripts.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
#include "../mwmechanics/aisetting.hpp"
|
#include "../mwmechanics/aisetting.hpp"
|
||||||
#include "../mwmechanics/autocalcspell.hpp"
|
#include "../mwmechanics/autocalcspell.hpp"
|
||||||
|
|
@ -620,8 +622,8 @@ namespace MWClass
|
||||||
float damage = 0.0f;
|
float damage = 0.0f;
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
othercls.onHit(
|
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
|
||||||
victim, damage, false, weapon, ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
|
damage, false, hitPosition, false, MWMechanics::DamageSourceType::Melee);
|
||||||
MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr);
|
MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr);
|
||||||
MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
|
MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
|
||||||
return;
|
return;
|
||||||
|
|
@ -694,14 +696,13 @@ namespace MWClass
|
||||||
|
|
||||||
MWMechanics::diseaseContact(victim, ptr);
|
MWMechanics::diseaseContact(victim, ptr);
|
||||||
|
|
||||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
|
||||||
|
damage, healthdmg, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Npc::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
void Npc::onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
|
||||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
const MWWorld::Ptr& attacker, bool successful, const MWMechanics::DamageSourceType sourceType) const
|
||||||
const MWMechanics::DamageSourceType sourceType) const
|
|
||||||
{
|
{
|
||||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
|
||||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
bool wasDead = stats.isDead();
|
bool wasDead = stats.isDead();
|
||||||
|
|
||||||
|
|
@ -748,23 +749,47 @@ namespace MWClass
|
||||||
if (!successful)
|
if (!successful)
|
||||||
{
|
{
|
||||||
// Missed
|
// Missed
|
||||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
|
||||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
stats.setLastHitObject(object.getCellRef().getRefId());
|
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (damage < 0.001f)
|
if (ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
|
||||||
damage = 0;
|
return;
|
||||||
|
|
||||||
bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
bool hasDamage = false;
|
||||||
|
bool hasHealthDamage = false;
|
||||||
|
float healthDamage = 0.f;
|
||||||
|
for (auto& [stat, damage] : damages)
|
||||||
|
{
|
||||||
|
if (damage < 0.001f)
|
||||||
|
continue;
|
||||||
|
hasDamage = true;
|
||||||
|
|
||||||
if (godmode)
|
if (stat == "health")
|
||||||
damage = 0;
|
{
|
||||||
|
hasHealthDamage = true;
|
||||||
|
healthDamage = damage;
|
||||||
|
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
||||||
|
health.setCurrent(health.getCurrent() - damage);
|
||||||
|
stats.setHealth(health);
|
||||||
|
}
|
||||||
|
else if (stat == "fatigue")
|
||||||
|
{
|
||||||
|
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||||
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||||
|
stats.setFatigue(fatigue);
|
||||||
|
}
|
||||||
|
else if (stat == "magicka")
|
||||||
|
{
|
||||||
|
MWMechanics::DynamicStat<float> magicka(getCreatureStats(ptr).getMagicka());
|
||||||
|
magicka.setCurrent(magicka.getCurrent() - damage);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (damage > 0.0f && !attacker.isEmpty())
|
if (hasDamage && !attacker.isEmpty())
|
||||||
{
|
{
|
||||||
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
||||||
// something, alert the character controller, scripts, etc.
|
// something, alert the character controller, scripts, etc.
|
||||||
|
|
@ -783,109 +808,16 @@ namespace MWClass
|
||||||
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
|
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
|
||||||
* gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
* gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
||||||
+ gmst.iKnockDownOddsBase->mValue.getInteger();
|
+ gmst.iKnockDownOddsBase->mValue.getInteger();
|
||||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
if (hasHealthDamage && agilityTerm <= healthDamage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||||
stats.setKnockedDown(true);
|
stats.setKnockedDown(true);
|
||||||
else
|
else
|
||||||
stats.setHitRecovery(true); // Is this supposed to always occur?
|
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||||
|
|
||||||
if (damage > 0 && ishealth)
|
|
||||||
{
|
|
||||||
// Hit percentages:
|
|
||||||
// cuirass = 30%
|
|
||||||
// shield, helmet, greaves, boots, pauldrons = 10% each
|
|
||||||
// guantlets = 5% each
|
|
||||||
static const int hitslots[20]
|
|
||||||
= { MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
|
|
||||||
MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
|
|
||||||
MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
|
|
||||||
MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft,
|
|
||||||
MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet,
|
|
||||||
MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves,
|
|
||||||
MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots,
|
|
||||||
MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron,
|
|
||||||
MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron,
|
|
||||||
MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet };
|
|
||||||
int hitslot = hitslots[Misc::Rng::rollDice(20, prng)];
|
|
||||||
|
|
||||||
float unmitigatedDamage = damage;
|
|
||||||
float x = damage / (damage + getArmorRating(ptr));
|
|
||||||
damage *= std::max(gmst.fCombatArmorMinMult->mValue.getFloat(), x);
|
|
||||||
int damageDiff = static_cast<int>(unmitigatedDamage - damage);
|
|
||||||
damage = std::max(1.f, damage);
|
|
||||||
damageDiff = std::max(1, damageDiff);
|
|
||||||
|
|
||||||
MWWorld::InventoryStore& inv = getInventoryStore(ptr);
|
|
||||||
MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
|
|
||||||
MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
|
|
||||||
bool hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId;
|
|
||||||
// If there's no item in the carried left slot or if it is not a shield redistribute the hit.
|
|
||||||
if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft)
|
|
||||||
{
|
|
||||||
if (Misc::Rng::rollDice(2, prng) == 0)
|
|
||||||
hitslot = MWWorld::InventoryStore::Slot_Cuirass;
|
|
||||||
else
|
|
||||||
hitslot = MWWorld::InventoryStore::Slot_LeftPauldron;
|
|
||||||
armorslot = inv.getSlot(hitslot);
|
|
||||||
if (armorslot != inv.end())
|
|
||||||
{
|
|
||||||
armor = *armorslot;
|
|
||||||
hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasArmor)
|
|
||||||
{
|
|
||||||
// Unarmed creature attacks don't affect armor condition unless it was
|
|
||||||
// explicitly requested.
|
|
||||||
if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()
|
|
||||||
|| Settings::game().mUnarmedCreatureAttacksDamageArmor)
|
|
||||||
{
|
|
||||||
int armorhealth = armor.getClass().getItemHealth(armor);
|
|
||||||
armorhealth -= std::min(damageDiff, armorhealth);
|
|
||||||
armor.getCellRef().setCharge(armorhealth);
|
|
||||||
|
|
||||||
// Armor broken? unequip it
|
|
||||||
if (armorhealth == 0)
|
|
||||||
armor = *inv.unequipItem(armor);
|
|
||||||
}
|
|
||||||
|
|
||||||
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
|
|
||||||
if (ptr == MWMechanics::getPlayer())
|
|
||||||
skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent);
|
|
||||||
|
|
||||||
if (skill == ESM::Skill::LightArmor)
|
|
||||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
|
|
||||||
else if (skill == ESM::Skill::MediumArmor)
|
|
||||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
|
|
||||||
else if (skill == ESM::Skill::HeavyArmor)
|
|
||||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
else if (ptr == MWMechanics::getPlayer())
|
|
||||||
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ishealth)
|
if (hasHealthDamage && healthDamage > 0.0f)
|
||||||
{
|
{
|
||||||
if (!attacker.isEmpty() && !godmode)
|
if (ptr == MWMechanics::getPlayer())
|
||||||
damage = scaleDamage(damage, attacker, ptr);
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
|
||||||
|
|
||||||
if (damage > 0.0f)
|
|
||||||
{
|
|
||||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
|
||||||
if (ptr == MWMechanics::getPlayer())
|
|
||||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
|
|
||||||
if (!attacker.isEmpty())
|
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
|
||||||
}
|
|
||||||
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
|
||||||
health.setCurrent(health.getCurrent() - damage);
|
|
||||||
stats.setHealth(health);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
|
||||||
stats.setFatigue(fatigue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasDead && getCreatureStats(ptr).isDead())
|
if (!wasDead && getCreatureStats(ptr).isDead())
|
||||||
|
|
@ -1136,8 +1068,17 @@ namespace MWClass
|
||||||
MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor);
|
MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Npc::getArmorRating(const MWWorld::Ptr& ptr) const
|
float Npc::getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
|
if (useLuaInterfaceIfAvailable && ptr == MWMechanics::getPlayer())
|
||||||
|
{
|
||||||
|
auto res = MWLua::LocalScripts::callPlayerInterface<float>("Combat", "getArmorRating");
|
||||||
|
if (res)
|
||||||
|
return res.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to the old engine implementation when actors don't have their scripts attached yet.
|
||||||
|
|
||||||
const MWWorld::Store<ESM::GameSetting>& store
|
const MWWorld::Store<ESM::GameSetting>& store
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
|
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
|
||||||
|
|
||||||
|
|
@ -1159,7 +1100,7 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr);
|
ratings[i] = it->getClass().getSkillAdjustedArmorRating(*it, ptr);
|
||||||
|
|
||||||
// Take in account armor condition
|
// Take in account armor condition
|
||||||
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
||||||
|
|
@ -1308,11 +1249,6 @@ namespace MWClass
|
||||||
return getNpcStats(ptr).getSkill(id).getModified();
|
return getNpcStats(ptr).getSkill(id).getModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Npc::getBloodTexture(const MWWorld::ConstPtr& ptr) const
|
|
||||||
{
|
|
||||||
return ptr.get<ESM::NPC>()->mBase->mBloodType;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Npc::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
|
void Npc::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
|
||||||
{
|
{
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ namespace MWClass
|
||||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
||||||
const osg::Vec3f& hitPosition, bool success) const override;
|
const osg::Vec3f& hitPosition, bool success) const override;
|
||||||
|
|
||||||
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
void onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
|
||||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
const MWWorld::Ptr& attacker, bool successful,
|
||||||
const MWMechanics::DamageSourceType sourceType) const override;
|
const MWMechanics::DamageSourceType sourceType) const override;
|
||||||
|
|
||||||
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const override;
|
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const override;
|
||||||
|
|
@ -112,7 +112,7 @@ namespace MWClass
|
||||||
///< Returns total weight of objects inside this object (including modifications from magic
|
///< Returns total weight of objects inside this object (including modifications from magic
|
||||||
/// effects). Throws an exception, if the object can't hold other objects.
|
/// effects). Throws an exception, if the object can't hold other objects.
|
||||||
|
|
||||||
float getArmorRating(const MWWorld::Ptr& ptr) const override;
|
float getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable) const override;
|
||||||
///< @return combined armor rating of this actor
|
///< @return combined armor rating of this actor
|
||||||
|
|
||||||
void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override;
|
void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override;
|
||||||
|
|
@ -137,9 +137,6 @@ namespace MWClass
|
||||||
|
|
||||||
float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
|
float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
|
||||||
|
|
||||||
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
|
|
||||||
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;
|
|
||||||
|
|
||||||
bool isNpc() const override { return true; }
|
bool isNpc() const override { return true; }
|
||||||
|
|
||||||
void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override;
|
void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override;
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ namespace MWClass
|
||||||
return std::make_pair(slots_, stack);
|
return std::make_pair(slots_, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::RefId Weapon::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
|
ESM::RefId Weapon::getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon>* ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon>* ref = ptr.get<ESM::Weapon>();
|
||||||
int type = ref->mBase->mData.mType;
|
int type = ref->mBase->mData.mType;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace MWClass
|
||||||
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
|
||||||
/// stay stacked when equipped?
|
/// stay stacked when equipped?
|
||||||
|
|
||||||
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
|
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const override;
|
||||||
|
|
||||||
int getValue(const MWWorld::ConstPtr& ptr) const override;
|
int getValue(const MWWorld::ConstPtr& ptr) const override;
|
||||||
///< Return trade value of the object. Throws an exception, if the object can't be traded.
|
///< Return trade value of the object. Throws an exception, if the object can't be traded.
|
||||||
|
|
|
||||||
|
|
@ -542,11 +542,10 @@ namespace MWGui
|
||||||
if (mPtr.isEmpty())
|
if (mPtr.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mArmorRating->setCaptionWithReplacing(
|
auto rating = MyGUI::utility::toString(static_cast<int>(mPtr.getClass().getArmorRating(mPtr, true)));
|
||||||
"#{sArmor}: " + MyGUI::utility::toString(static_cast<int>(mPtr.getClass().getArmorRating(mPtr))));
|
mArmorRating->setCaptionWithReplacing("#{sArmor}: " + rating);
|
||||||
if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
|
if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
|
||||||
mArmorRating->setCaptionWithReplacing(
|
mArmorRating->setCaptionWithReplacing(rating);
|
||||||
MyGUI::utility::toString(static_cast<int>(mPtr.getClass().getArmorRating(mPtr))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InventoryWindow::updatePreviewSize()
|
void InventoryWindow::updatePreviewSize()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <components/lua/util.hpp>
|
#include <components/lua/util.hpp>
|
||||||
#include <components/misc/strings/algorithm.hpp>
|
#include <components/misc/strings/algorithm.hpp>
|
||||||
#include <components/misc/strings/lower.hpp>
|
#include <components/misc/strings/lower.hpp>
|
||||||
|
#include <components/settings/values.hpp>
|
||||||
#include <components/version/version.hpp>
|
#include <components/version/version.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
@ -159,6 +160,8 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api["getGameDifficulty"] = []() { return Settings::game().mDifficulty.get(); };
|
||||||
|
|
||||||
sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
|
sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
|
||||||
return context.setTypePackage(readOnlyApi, "openmw_core");
|
return context.setTypePackage(readOnlyApi, "openmw_core");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <components/lua/scriptscontainer.hpp>
|
#include <components/lua/scriptscontainer.hpp>
|
||||||
|
|
||||||
#include "../mwbase/luamanager.hpp"
|
#include "../mwbase/luamanager.hpp"
|
||||||
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
|
||||||
|
|
@ -91,6 +92,19 @@ namespace MWLua
|
||||||
|
|
||||||
void applyStatsCache();
|
void applyStatsCache();
|
||||||
|
|
||||||
|
// Calls a lua interface on the player's scripts. This call is only meant for use in updating UI elements.
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
static std::optional<T> callPlayerInterface(
|
||||||
|
std::string_view interfaceName, std::string_view identifier, const Args&... args)
|
||||||
|
{
|
||||||
|
auto player = MWMechanics::getPlayer();
|
||||||
|
auto scripts = player.getRefData().getLuaScripts();
|
||||||
|
if (scripts)
|
||||||
|
return scripts->callInterface<T>(interfaceName, identifier, args...);
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SelfObject mData;
|
SelfObject mData;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
#include <MyGUI_InputManager.h>
|
#include <MyGUI_InputManager.h>
|
||||||
#include <osg/Stats>
|
#include <osg/Stats>
|
||||||
|
|
||||||
#include "sol/state_view.hpp"
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/esm/luascripts.hpp>
|
#include <components/esm/luascripts.hpp>
|
||||||
|
|
@ -152,6 +150,17 @@ namespace MWLua
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaManager::sendLocalEvent(
|
||||||
|
const MWWorld::Ptr& target, const std::string& name, const std::optional<sol::table>& data)
|
||||||
|
{
|
||||||
|
LuaUtil::BinaryData binary = {};
|
||||||
|
if (data)
|
||||||
|
{
|
||||||
|
binary = LuaUtil::serialize(*data, mLocalSerializer.get());
|
||||||
|
}
|
||||||
|
mLuaEvents.addLocalEvent({ getId(target), name, binary });
|
||||||
|
}
|
||||||
|
|
||||||
void LuaManager::update()
|
void LuaManager::update()
|
||||||
{
|
{
|
||||||
if (const int steps = Settings::lua().mGcStepsPerFrame; steps > 0)
|
if (const int steps = Settings::lua().mGcStepsPerFrame; steps > 0)
|
||||||
|
|
@ -482,6 +491,49 @@ namespace MWLua
|
||||||
EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) });
|
EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaManager::onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,
|
||||||
|
const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth,
|
||||||
|
const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType)
|
||||||
|
{
|
||||||
|
mLua.protectedCall([&](LuaUtil::LuaView& view) {
|
||||||
|
sol::table damageTable = view.newTable();
|
||||||
|
if (isHealth)
|
||||||
|
damageTable["health"] = damage;
|
||||||
|
else
|
||||||
|
damageTable["fatigue"] = damage;
|
||||||
|
|
||||||
|
sol::table data = view.newTable();
|
||||||
|
if (!attacker.isEmpty())
|
||||||
|
data["attacker"] = LObject(attacker);
|
||||||
|
if (!weapon.isEmpty())
|
||||||
|
data["weapon"] = LObject(weapon);
|
||||||
|
if (!ammo.isEmpty())
|
||||||
|
data["ammo"] = LObject(weapon);
|
||||||
|
data["type"] = attackType;
|
||||||
|
data["strength"] = attackStrength;
|
||||||
|
data["damage"] = damageTable;
|
||||||
|
data["hitPos"] = hitPos;
|
||||||
|
data["successful"] = successful;
|
||||||
|
switch (sourceType)
|
||||||
|
{
|
||||||
|
case MWMechanics::DamageSourceType::Unspecified:
|
||||||
|
data["sourceType"] = "unspecified";
|
||||||
|
break;
|
||||||
|
case MWMechanics::DamageSourceType::Melee:
|
||||||
|
data["sourceType"] = "melee";
|
||||||
|
break;
|
||||||
|
case MWMechanics::DamageSourceType::Ranged:
|
||||||
|
data["sourceType"] = "ranged";
|
||||||
|
break;
|
||||||
|
case MWMechanics::DamageSourceType::Magical:
|
||||||
|
data["sourceType"] = "magic";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLocalEvent(victim, "Hit", data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@ namespace MWLua
|
||||||
bool loopfallback) override;
|
bool loopfallback) override;
|
||||||
void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override;
|
void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override;
|
||||||
void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override;
|
void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override;
|
||||||
|
void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,
|
||||||
|
const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth,
|
||||||
|
const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) override;
|
||||||
void exteriorCreated(MWWorld::CellStore& cell) override
|
void exteriorCreated(MWWorld::CellStore& cell) override
|
||||||
{
|
{
|
||||||
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });
|
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });
|
||||||
|
|
@ -166,6 +169,9 @@ namespace MWLua
|
||||||
LuaUtil::InputAction::Registry& inputActions() { return mInputActions; }
|
LuaUtil::InputAction::Registry& inputActions() { return mInputActions; }
|
||||||
LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; }
|
LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; }
|
||||||
|
|
||||||
|
void sendLocalEvent(
|
||||||
|
const MWWorld::Ptr& target, const std::string& name, const std::optional<sol::table>& data = std::nullopt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initConfiguration();
|
void initConfiguration();
|
||||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
|
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
|
||||||
|
|
|
||||||
|
|
@ -976,9 +976,6 @@ namespace MWLua
|
||||||
bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration);
|
bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
||||||
|
|
||||||
bool appliedOnce = mgef->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
|
||||||
if (!appliedOnce)
|
|
||||||
effect.mDuration = std::max(1.f, effect.mDuration);
|
|
||||||
effect.mTimeLeft = effect.mDuration;
|
effect.mTimeLeft = effect.mDuration;
|
||||||
params.getEffects().emplace_back(effect);
|
params.getEffects().emplace_back(effect);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,7 @@ namespace MWLua
|
||||||
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
||||||
DetourNavigator::AreaCosts areaCosts{};
|
DetourNavigator::AreaCosts areaCosts{};
|
||||||
float destinationTolerance = 1;
|
float destinationTolerance = 1;
|
||||||
|
std::vector<osg::Vec3f> checkpoints;
|
||||||
|
|
||||||
if (options.has_value())
|
if (options.has_value())
|
||||||
{
|
{
|
||||||
|
|
@ -258,13 +259,24 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
||||||
destinationTolerance = *v;
|
destinationTolerance = *v;
|
||||||
|
if (const auto& t = options->get<sol::optional<sol::table>>("checkpoints"))
|
||||||
|
{
|
||||||
|
for (const auto& [k, v] : *t)
|
||||||
|
{
|
||||||
|
const int index = k.as<int>();
|
||||||
|
const osg::Vec3f position = v.as<osg::Vec3f>();
|
||||||
|
if (index != static_cast<int>(checkpoints.size() + 1))
|
||||||
|
throw std::runtime_error("checkpoints is not an array");
|
||||||
|
checkpoints.push_back(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<osg::Vec3f> path;
|
std::vector<osg::Vec3f> path;
|
||||||
|
|
||||||
const DetourNavigator::Status status
|
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||||
= DetourNavigator::findPath(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds,
|
*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, source, destination,
|
||||||
source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(path));
|
includeFlags, areaCosts, destinationTolerance, checkpoints, std::back_inserter(path));
|
||||||
|
|
||||||
sol::table result(lua, sol::create);
|
sol::table result(lua, sol::create);
|
||||||
LuaUtil::copyVectorToTable(path, result);
|
LuaUtil::copyVectorToTable(path, result);
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,42 @@ namespace MWLua
|
||||||
return ptr.getClass().getCapacity(ptr);
|
return ptr.getClass().getCapacity(ptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
actor["_onHit"] = [context](const SelfObject& self, const sol::table& options) {
|
||||||
|
sol::optional<sol::table> damageLua = options.get<sol::optional<sol::table>>("damage");
|
||||||
|
std::map<std::string, float> damageCpp;
|
||||||
|
if (damageLua)
|
||||||
|
{
|
||||||
|
for (auto& [key, value] : damageLua.value())
|
||||||
|
{
|
||||||
|
damageCpp[key.as<std::string>()] = value.as<float>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string sourceTypeStr = options.get_or<std::string>("sourceType", "unspecified");
|
||||||
|
MWMechanics::DamageSourceType sourceType = MWMechanics::DamageSourceType::Unspecified;
|
||||||
|
if (sourceTypeStr == "melee")
|
||||||
|
sourceType = MWMechanics::DamageSourceType::Melee;
|
||||||
|
else if (sourceTypeStr == "ranged")
|
||||||
|
sourceType = MWMechanics::DamageSourceType::Ranged;
|
||||||
|
else if (sourceTypeStr == "magic")
|
||||||
|
sourceType = MWMechanics::DamageSourceType::Magical;
|
||||||
|
sol::optional<Object> weapon = options.get<sol::optional<Object>>("weapon");
|
||||||
|
sol::optional<Object> ammo = options.get<sol::optional<Object>>("ammo");
|
||||||
|
|
||||||
|
context.mLuaManager->addAction(
|
||||||
|
[self = self, damages = std::move(damageCpp), attacker = options.get<sol::optional<Object>>("attacker"),
|
||||||
|
weapon = ammo ? ammo : weapon, successful = options.get<bool>("successful"),
|
||||||
|
sourceType = sourceType] {
|
||||||
|
MWWorld::Ptr attackerPtr;
|
||||||
|
MWWorld::Ptr weaponPtr;
|
||||||
|
if (attacker)
|
||||||
|
attackerPtr = attacker->ptr();
|
||||||
|
if (weapon)
|
||||||
|
weaponPtr = weapon->ptr();
|
||||||
|
self.ptr().getClass().onHit(self.ptr(), damages, weaponPtr, attackerPtr, successful, sourceType);
|
||||||
|
},
|
||||||
|
"HitAction");
|
||||||
|
};
|
||||||
|
|
||||||
addActorStatsBindings(actor, context);
|
addActorStatsBindings(actor, context);
|
||||||
addActorMagicBindings(actor, context);
|
addActorMagicBindings(actor, context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ namespace MWLua
|
||||||
[](const ESM::Creature& rec) -> bool { return rec.mFlags & ESM::Creature::Essential; });
|
[](const ESM::Creature& rec) -> bool { return rec.mFlags & ESM::Creature::Essential; });
|
||||||
record["isRespawning"] = sol::readonly_property(
|
record["isRespawning"] = sol::readonly_property(
|
||||||
[](const ESM::Creature& rec) -> bool { return rec.mFlags & ESM::Creature::Respawn; });
|
[](const ESM::Creature& rec) -> bool { return rec.mFlags & ESM::Creature::Respawn; });
|
||||||
|
record["bloodType"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mBloodType; });
|
||||||
|
|
||||||
addActorServicesBindings<ESM::Creature>(record, context);
|
addActorServicesBindings<ESM::Creature>(record, context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ namespace MWLua
|
||||||
record["isRespawning"]
|
record["isRespawning"]
|
||||||
= sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.mFlags & ESM::NPC::Respawn; });
|
= sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.mFlags & ESM::NPC::Respawn; });
|
||||||
record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; });
|
record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; });
|
||||||
|
record["bloodType"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mBloodType; });
|
||||||
addActorServicesBindings<ESM::NPC>(record, context);
|
addActorServicesBindings<ESM::NPC>(record, context);
|
||||||
|
|
||||||
npc["classes"] = initClassRecordBindings(context);
|
npc["classes"] = initClassRecordBindings(context);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ namespace MWMechanics
|
||||||
CharacterController mCharacterController;
|
CharacterController mCharacterController;
|
||||||
int mGreetingTimer{ 0 };
|
int mGreetingTimer{ 0 };
|
||||||
float mTargetAngleRadians{ 0.f };
|
float mTargetAngleRadians{ 0.f };
|
||||||
GreetingState mGreetingState{ Greet_None };
|
GreetingState mGreetingState{ GreetingState::None };
|
||||||
Misc::DeviatingPeriodicTimer mEngageCombat{ 1.0f, 0.25f,
|
Misc::DeviatingPeriodicTimer mEngageCombat{ 1.0f, 0.25f,
|
||||||
Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng()) };
|
Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng()) };
|
||||||
bool mIsTurningToPlayer{ false };
|
bool mIsTurningToPlayer{ false };
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
#include "attacktype.hpp"
|
#include "attacktype.hpp"
|
||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
|
#include "greetingstate.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
|
|
@ -487,7 +488,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
actorState.setTurningToPlayer(false);
|
actorState.setTurningToPlayer(false);
|
||||||
actorState.setGreetingTimer(0);
|
actorState.setGreetingTimer(0);
|
||||||
actorState.setGreetingState(Greet_None);
|
actorState.setGreetingState(GreetingState::None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,7 +526,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
int greetingTimer = actorState.getGreetingTimer();
|
int greetingTimer = actorState.getGreetingTimer();
|
||||||
GreetingState greetingState = actorState.getGreetingState();
|
GreetingState greetingState = actorState.getGreetingState();
|
||||||
if (greetingState == Greet_None)
|
if (greetingState == GreetingState::None)
|
||||||
{
|
{
|
||||||
if ((playerPos - actorPos).length2() <= helloDistance * helloDistance && !playerStats.isDead()
|
if ((playerPos - actorPos).length2() <= helloDistance * helloDistance && !playerStats.isDead()
|
||||||
&& !actorStats.isParalyzed() && !isTargetMagicallyHidden(player)
|
&& !actorStats.isParalyzed() && !isTargetMagicallyHidden(player)
|
||||||
|
|
@ -535,14 +536,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (greetingTimer >= GREETING_SHOULD_START)
|
if (greetingTimer >= GREETING_SHOULD_START)
|
||||||
{
|
{
|
||||||
greetingState = Greet_InProgress;
|
greetingState = GreetingState::InProgress;
|
||||||
if (!MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("hello")))
|
if (!MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("hello")))
|
||||||
greetingState = Greet_Done;
|
greetingState = GreetingState::Done;
|
||||||
greetingTimer = 0;
|
greetingTimer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (greetingState == Greet_InProgress)
|
if (greetingState == GreetingState::InProgress)
|
||||||
{
|
{
|
||||||
greetingTimer++;
|
greetingTimer++;
|
||||||
|
|
||||||
|
|
@ -554,16 +555,16 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (greetingTimer >= GREETING_COOLDOWN)
|
if (greetingTimer >= GREETING_COOLDOWN)
|
||||||
{
|
{
|
||||||
greetingState = Greet_Done;
|
greetingState = GreetingState::Done;
|
||||||
greetingTimer = 0;
|
greetingTimer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (greetingState == Greet_Done)
|
if (greetingState == GreetingState::Done)
|
||||||
{
|
{
|
||||||
float resetDist = 2 * helloDistance;
|
float resetDist = 2 * helloDistance;
|
||||||
if ((playerPos - actorPos).length2() >= resetDist * resetDist)
|
if ((playerPos - actorPos).length2() >= resetDist * resetDist)
|
||||||
greetingState = Greet_None;
|
greetingState = GreetingState::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
actorState.setGreetingTimer(greetingTimer);
|
actorState.setGreetingTimer(greetingTimer);
|
||||||
|
|
@ -2381,7 +2382,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
const auto it = mIndex.find(ptr.mRef);
|
const auto it = mIndex.find(ptr.mRef);
|
||||||
if (it == mIndex.end())
|
if (it == mIndex.end())
|
||||||
return Greet_None;
|
return GreetingState::None;
|
||||||
|
|
||||||
return it->second->getGreetingState();
|
return it->second->getGreetingState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
#include "aicombat.hpp"
|
#include "aicombat.hpp"
|
||||||
|
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
|
||||||
#include <components/misc/rng.hpp>
|
|
||||||
|
|
||||||
#include <components/esm3/aisequence.hpp>
|
|
||||||
|
|
||||||
#include <components/misc/mathutil.hpp>
|
|
||||||
|
|
||||||
#include <components/detournavigator/navigatorutils.hpp>
|
#include <components/detournavigator/navigatorutils.hpp>
|
||||||
|
#include <components/esm3/aisequence.hpp>
|
||||||
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
|
#include <components/misc/mathutil.hpp>
|
||||||
|
#include <components/misc/pathgridutils.hpp>
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
#include "../mwphysics/raycasting.hpp"
|
#include "../mwphysics/raycasting.hpp"
|
||||||
|
|
@ -303,8 +301,8 @@ namespace MWMechanics
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
||||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds,
|
mPathFinder.buildPath(actor, vActorPos, vTargetPos, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
storage.mAttackRange, PathType::Full);
|
||||||
|
|
||||||
if (!mPathFinder.isPathConstructed())
|
if (!mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
|
|
@ -317,8 +315,8 @@ namespace MWMechanics
|
||||||
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
||||||
{
|
{
|
||||||
// If the point is close enough, try to find a path to that point.
|
// If the point is close enough, try to find a path to that point.
|
||||||
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds,
|
mPathFinder.buildPath(actor, vActorPos, *hit, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
storage.mAttackRange, PathType::Full);
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
// If path to that point is found use it as custom destination.
|
// If path to that point is found use it as custom destination.
|
||||||
|
|
@ -394,8 +392,8 @@ namespace MWMechanics
|
||||||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||||
coords.toLocal(localPos);
|
coords.toLocal(localPos);
|
||||||
|
|
||||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, localPos);
|
||||||
for (size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
for (std::size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||||
{
|
{
|
||||||
if (i != closestPointIndex
|
if (i != closestPointIndex
|
||||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
||||||
|
|
@ -456,7 +454,8 @@ namespace MWMechanics
|
||||||
float dist
|
float dist
|
||||||
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||||
if ((dist > fFleeDistance && !storage.mLOS)
|
if ((dist > fFleeDistance && !storage.mLOS)
|
||||||
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration, supportedMovementDirections))
|
|| pathTo(
|
||||||
|
actor, Misc::Convert::makeOsgVec3f(storage.mFleeDest), duration, supportedMovementDirections))
|
||||||
{
|
{
|
||||||
state = AiCombatStorage::FleeState_Idle;
|
state = AiCombatStorage::FleeState_Idle;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
#define GAME_MWMECHANICS_AICOMBAT_H
|
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||||
|
|
||||||
#include "aitemporarybase.hpp"
|
#include "aitemporarybase.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
#include "movement.hpp"
|
||||||
#include "typedaipackage.hpp"
|
#include "typedaipackage.hpp"
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp" // for Doors
|
#include "../mwworld/cellstore.hpp" // for Doors
|
||||||
|
|
||||||
#include "aitimer.hpp"
|
#include <components/esm3/loadpgrd.hpp>
|
||||||
#include "movement.hpp"
|
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
= world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
= world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid),
|
mPathFinder.buildLimitedPath(actor, position, dest, getPathGridGraph(pathgrid), agentBounds,
|
||||||
agentBounds, navigatorFlags, areaCosts, endTolerance, pathType);
|
navigatorFlags, areaCosts, endTolerance, pathType);
|
||||||
mRotateOnTheRunChecks = 3;
|
mRotateOnTheRunChecks = 3;
|
||||||
|
|
||||||
// give priority to go directly on target if there is minimal opportunity
|
// give priority to go directly on target if there is minimal opportunity
|
||||||
|
|
@ -501,7 +501,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::
|
||||||
result |= DetourNavigator::Flag_swim;
|
result |= DetourNavigator::Flag_swim;
|
||||||
|
|
||||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
||||||
result |= DetourNavigator::Flag_walk | DetourNavigator::Flag_usePathgrid;
|
{
|
||||||
|
result |= DetourNavigator::Flag_walk;
|
||||||
|
if (getTypeId() != AiPackageTypeId::Wander)
|
||||||
|
result |= DetourNavigator::Flag_usePathgrid;
|
||||||
|
}
|
||||||
|
|
||||||
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
||||||
result |= DetourNavigator::Flag_openDoor;
|
result |= DetourNavigator::Flag_openDoor;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct Cell;
|
struct Cell;
|
||||||
|
struct Pathgrid;
|
||||||
|
|
||||||
namespace AiSequence
|
namespace AiSequence
|
||||||
{
|
{
|
||||||
struct AiSequence;
|
struct AiSequence;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
|
#include "greetingstate.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
|
@ -77,7 +78,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump)
|
if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump)
|
||||||
&& !stats.getMovementFlag(CreatureStats::Flag_ForceSneak)
|
&& !stats.getMovementFlag(CreatureStats::Flag_ForceSneak)
|
||||||
&& (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress))
|
&& (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == GreetingState::InProgress))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <components/detournavigator/navigatorutils.hpp>
|
#include <components/detournavigator/navigatorutils.hpp>
|
||||||
#include <components/esm3/aisequence.hpp>
|
#include <components/esm3/aisequence.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
|
#include <components/misc/pathgridutils.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
@ -24,22 +25,12 @@
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
|
#include "greetingstate.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "pathgrid.hpp"
|
#include "pathgrid.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const int COUNT_BEFORE_RESET = 10;
|
|
||||||
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
|
|
||||||
|
|
||||||
// to prevent overcrowding
|
|
||||||
static const int DESTINATION_TOLERANCE = 64;
|
|
||||||
|
|
||||||
// distance must be long enough that NPC will need to move to get there.
|
|
||||||
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2;
|
|
||||||
|
|
||||||
static const std::size_t MAX_IDLE_SIZE = 8;
|
|
||||||
|
|
||||||
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||||
"idle2",
|
"idle2",
|
||||||
"idle3",
|
"idle3",
|
||||||
|
|
@ -53,11 +44,22 @@ namespace MWMechanics
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
constexpr int countBeforeReset = 10;
|
||||||
|
constexpr float idlePositionCheckInterval = 1.5f;
|
||||||
|
|
||||||
|
// to prevent overcrowding
|
||||||
|
constexpr unsigned destinationTolerance = 64;
|
||||||
|
|
||||||
|
// distance must be long enough that NPC will need to move to get there.
|
||||||
|
constexpr unsigned minimumWanderDistance = destinationTolerance * 2;
|
||||||
|
|
||||||
|
constexpr std::size_t maxIdleSize = 8;
|
||||||
|
|
||||||
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
|
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
||||||
return 1;
|
return 1;
|
||||||
return COUNT_BEFORE_RESET;
|
return countBeforeReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
|
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
|
||||||
|
|
@ -99,16 +101,42 @@ namespace MWMechanics
|
||||||
|
|
||||||
std::vector<unsigned char> getInitialIdle(const std::vector<unsigned char>& idle)
|
std::vector<unsigned char> getInitialIdle(const std::vector<unsigned char>& idle)
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> result(MAX_IDLE_SIZE, 0);
|
std::vector<unsigned char> result(maxIdleSize, 0);
|
||||||
std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin());
|
std::copy_n(idle.begin(), std::min(maxIdleSize, idle.size()), result.begin());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE])
|
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[maxIdleSize])
|
||||||
{
|
{
|
||||||
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
|
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void trimAllowedPositions(const std::deque<osg::Vec3f>& path, std::vector<osg::Vec3f>& allowedPositions)
|
||||||
|
{
|
||||||
|
// TODO: how to add these back in once the door opens?
|
||||||
|
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
||||||
|
// Every now and then check whether one of the doors is opened. (maybe
|
||||||
|
// at the end of playing idle?) If the door is opened then re-calculate
|
||||||
|
// allowed positions starting from the spawn point.
|
||||||
|
std::vector<osg::Vec3f> points(path.begin(), path.end());
|
||||||
|
while (points.size() >= 2)
|
||||||
|
{
|
||||||
|
const osg::Vec3f point = points.back();
|
||||||
|
for (std::size_t j = 0; j < allowedPositions.size(); j++)
|
||||||
|
{
|
||||||
|
// FIXME: doesn't handle a door with the same X/Y
|
||||||
|
// coordinates but with a different Z
|
||||||
|
if (std::abs(allowedPositions[j].x() - point.x()) <= 0.5
|
||||||
|
&& std::abs(allowedPositions[j].y() - point.y()) <= 0.5)
|
||||||
|
{
|
||||||
|
allowedPositions.erase(allowedPositions.begin() + j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
points.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AiWanderStorage::AiWanderStorage()
|
AiWanderStorage::AiWanderStorage()
|
||||||
|
|
@ -118,9 +146,9 @@ namespace MWMechanics
|
||||||
, mCanWanderAlongPathGrid(true)
|
, mCanWanderAlongPathGrid(true)
|
||||||
, mIdleAnimation(0)
|
, mIdleAnimation(0)
|
||||||
, mBadIdles()
|
, mBadIdles()
|
||||||
, mPopulateAvailableNodes(true)
|
, mPopulateAvailablePositions(true)
|
||||||
, mAllowedNodes()
|
, mAllowedPositions()
|
||||||
, mTrimCurrentNode(false)
|
, mTrimCurrentPosition(false)
|
||||||
, mCheckIdlePositionTimer(0)
|
, mCheckIdlePositionTimer(0)
|
||||||
, mStuckCount(0)
|
, mStuckCount(0)
|
||||||
{
|
{
|
||||||
|
|
@ -128,8 +156,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat)
|
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat)
|
||||||
: TypedAiPackage<AiWander>(repeat)
|
: TypedAiPackage<AiWander>(repeat)
|
||||||
, mDistance(std::max(0, distance))
|
, mDistance(static_cast<unsigned>(std::max(0, distance)))
|
||||||
, mDuration(std::max(0, duration))
|
, mDuration(static_cast<unsigned>(std::max(0, duration)))
|
||||||
, mRemainingDuration(duration)
|
, mRemainingDuration(duration)
|
||||||
, mTimeOfDay(timeOfDay)
|
, mTimeOfDay(timeOfDay)
|
||||||
, mIdle(getInitialIdle(idle))
|
, mIdle(getInitialIdle(idle))
|
||||||
|
|
@ -215,20 +243,12 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
const ESM::Pathgrid* pathgrid
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
if (mUsePathgrid)
|
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||||
{
|
constexpr float endTolerance = 0;
|
||||||
mPathFinder.buildPathByPathgrid(
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid));
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
}
|
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, getPathGridGraph(pathgrid), agentBounds,
|
||||||
else
|
navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
||||||
{
|
|
||||||
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
|
||||||
constexpr float endTolerance = 0;
|
|
||||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
|
||||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid),
|
|
||||||
agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
||||||
|
|
@ -238,7 +258,7 @@ namespace MWMechanics
|
||||||
&& !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak))
|
&& !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak))
|
||||||
{
|
{
|
||||||
GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
|
GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
|
||||||
if (greetingState == Greet_InProgress)
|
if (greetingState == GreetingState::InProgress)
|
||||||
{
|
{
|
||||||
if (storage.mState == AiWanderStorage::Wander_Walking)
|
if (storage.mState == AiWanderStorage::Wander_Walking)
|
||||||
{
|
{
|
||||||
|
|
@ -259,9 +279,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
{
|
{
|
||||||
if (mDistance <= 0)
|
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
|
||||||
|
|
||||||
if (isPackageCompleted())
|
if (isPackageCompleted())
|
||||||
{
|
{
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
|
|
@ -276,13 +293,15 @@ namespace MWMechanics
|
||||||
mStoredInitialActorPosition = true;
|
mStoredInitialActorPosition = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization to discover & store allowed node points for this actor.
|
// Initialization to discover & store allowed positions points for this actor.
|
||||||
if (storage.mPopulateAvailableNodes)
|
if (storage.mPopulateAvailablePositions)
|
||||||
{
|
{
|
||||||
getAllowedNodes(actor, storage);
|
fillAllowedPositions(actor, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
|
|
||||||
|
auto& prng = world.getPrng();
|
||||||
if (canActorMoveByZAxis(actor) && mDistance > 0)
|
if (canActorMoveByZAxis(actor) && mDistance > 0)
|
||||||
{
|
{
|
||||||
// Typically want to idle for a short time before the next wander
|
// Typically want to idle for a short time before the next wander
|
||||||
|
|
@ -295,7 +314,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
// If the package has a wander distance but no pathgrid is available,
|
// If the package has a wander distance but no pathgrid is available,
|
||||||
// randomly idle or wander near spawn point
|
// randomly idle or wander near spawn point
|
||||||
else if (storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
else if (storage.mAllowedPositions.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
||||||
{
|
{
|
||||||
// Typically want to idle for a short time before the next wander
|
// Typically want to idle for a short time before the next wander
|
||||||
if (Misc::Rng::rollDice(100, prng) >= 96)
|
if (Misc::Rng::rollDice(100, prng) >= 96)
|
||||||
|
|
@ -307,7 +326,7 @@ namespace MWMechanics
|
||||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually)
|
else if (storage.mAllowedPositions.empty() && !storage.mIsWanderingManually)
|
||||||
{
|
{
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
storage.mCanWanderAlongPathGrid = false;
|
||||||
}
|
}
|
||||||
|
|
@ -323,9 +342,9 @@ namespace MWMechanics
|
||||||
// Construct a new path if there isn't one
|
// Construct a new path if there isn't one
|
||||||
if (!mPathFinder.isPathConstructed())
|
if (!mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
if (!storage.mAllowedNodes.empty())
|
if (!storage.mAllowedPositions.empty())
|
||||||
{
|
{
|
||||||
setPathToAnAllowedNode(actor, storage, pos);
|
setPathToAnAllowedPosition(actor, storage, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,7 +355,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking
|
if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking
|
||||||
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
||||||
|| isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
|| world.isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||||
completeManualWalking(actor, storage);
|
completeManualWalking(actor, storage);
|
||||||
|
|
||||||
return false; // AiWander package not yet completed
|
return false; // AiWander package not yet completed
|
||||||
|
|
@ -366,12 +385,12 @@ namespace MWMechanics
|
||||||
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
||||||
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||||
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
const auto agentBounds = world->getPathfindingAgentBounds(actor);
|
const auto agentBounds = world.getPathfindingAgentBounds(actor);
|
||||||
const auto navigator = world->getNavigator();
|
const auto navigator = world.getNavigator();
|
||||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
Misc::Rng::Generator& prng = world.getPrng();
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
@ -411,7 +430,7 @@ namespace MWMechanics
|
||||||
if (isDestinationHidden(actor, mDestination))
|
if (isDestinationHidden(actor, mDestination))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (isAreaOccupiedByOtherActor(actor, mDestination))
|
if (world.isAreaOccupiedByOtherActor(actor, mDestination))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
constexpr float endTolerance = 0;
|
constexpr float endTolerance = 0;
|
||||||
|
|
@ -491,24 +510,24 @@ namespace MWMechanics
|
||||||
|
|
||||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
|
// Check if an idle actor is too far from all allowed positions or too close to a door - if so start walking.
|
||||||
storage.mCheckIdlePositionTimer += duration;
|
storage.mCheckIdlePositionTimer += duration;
|
||||||
|
|
||||||
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
if (storage.mCheckIdlePositionTimer >= idlePositionCheckInterval && !isStationary())
|
||||||
{
|
{
|
||||||
storage.mCheckIdlePositionTimer = 0; // restart timer
|
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
||||||
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
|
if (proximityToDoor(actor, distance) || !isNearAllowedPosition(actor, storage, distance))
|
||||||
{
|
{
|
||||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
storage.mTrimCurrentNode = false; // just in case
|
storage.mTrimCurrentPosition = false; // just in case
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if idle animation finished
|
// Check if idle animation finished
|
||||||
GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
|
GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
|
||||||
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
|
if (!checkIdle(actor, storage.mIdleAnimation) && greetingState != GreetingState::InProgress)
|
||||||
{
|
{
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
||||||
|
|
@ -517,16 +536,14 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
bool AiWander::isNearAllowedPosition(
|
||||||
|
const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||||
{
|
{
|
||||||
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
const float squaredDistance = distance * distance;
|
||||||
{
|
return std::ranges::find_if(storage.mAllowedPositions, [&](const osg::Vec3& v) {
|
||||||
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
return (actorPos - v).length2() < squaredDistance;
|
||||||
if ((actorPos - point).length2() < distance * distance)
|
}) != storage.mAllowedPositions.end();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
||||||
|
|
@ -535,7 +552,7 @@ namespace MWMechanics
|
||||||
// Is there no destination or are we there yet?
|
// Is there no destination or are we there yet?
|
||||||
if ((!mPathFinder.isPathConstructed())
|
if ((!mPathFinder.isPathConstructed())
|
||||||
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
||||||
DESTINATION_TOLERANCE))
|
destinationTolerance))
|
||||||
{
|
{
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
|
|
@ -586,8 +603,8 @@ namespace MWMechanics
|
||||||
if (proximityToDoor(actor, distance))
|
if (proximityToDoor(actor, distance))
|
||||||
{
|
{
|
||||||
// remove allowed points then select another random destination
|
// remove allowed points then select another random destination
|
||||||
storage.mTrimCurrentNode = true;
|
storage.mTrimCurrentPosition = true;
|
||||||
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
trimAllowedPositions(mPathFinder.getPath(), storage.mAllowedPositions);
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
|
|
@ -606,67 +623,67 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::setPathToAnAllowedNode(
|
void AiWander::setPathToAnAllowedPosition(
|
||||||
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||||
{
|
{
|
||||||
auto world = MWBase::Environment::get().getWorld();
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
auto& prng = world->getPrng();
|
Misc::Rng::Generator& prng = world.getPrng();
|
||||||
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
const std::size_t randomAllowedPositionIndex
|
||||||
const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode];
|
= static_cast<std::size_t>(Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng));
|
||||||
|
const osg::Vec3f randomAllowedPosition = storage.mAllowedPositions[randomAllowedPositionIndex];
|
||||||
|
|
||||||
const osg::Vec3f start = actorPos.asVec3();
|
const osg::Vec3f start = actorPos.asVec3();
|
||||||
|
|
||||||
// don't take shortcuts for wandering
|
const MWWorld::Cell& cell = *actor.getCell()->getCell();
|
||||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
const ESM::Pathgrid* pathgrid = world.getStore().get<ESM::Pathgrid>().search(cell);
|
||||||
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
|
const PathgridGraph& pathgridGraph = getPathGridGraph(pathgrid);
|
||||||
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(pathgrid));
|
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(cell);
|
||||||
|
std::deque<ESM::Pathgrid::Point> path
|
||||||
|
= pathgridGraph.aStarSearch(Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(start)),
|
||||||
|
Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(randomAllowedPosition)));
|
||||||
|
|
||||||
|
// Choose a different position and delete this one from possible positions because it is uncreachable:
|
||||||
|
if (path.empty())
|
||||||
{
|
{
|
||||||
mDestination = destVec3f;
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||||
mHasDestination = true;
|
return;
|
||||||
mUsePathgrid = true;
|
|
||||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same
|
|
||||||
// node):
|
|
||||||
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
|
||||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
|
||||||
// check if mCurrentNode was taken out of mAllowedNodes
|
|
||||||
if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1)
|
|
||||||
storage.mTrimCurrentNode = false;
|
|
||||||
else
|
|
||||||
storage.mAllowedNodes.push_back(storage.mCurrentNode);
|
|
||||||
storage.mCurrentNode = temp;
|
|
||||||
|
|
||||||
storage.setState(AiWanderStorage::Wander_Walking);
|
|
||||||
}
|
}
|
||||||
// Choose a different node and delete this one from possible nodes because it is uncreachable:
|
|
||||||
|
// Drop nearest pathgrid point.
|
||||||
|
path.pop_front();
|
||||||
|
|
||||||
|
std::vector<osg::Vec3f> checkpoints(path.size());
|
||||||
|
for (std::size_t i = 0; i < path.size(); ++i)
|
||||||
|
checkpoints[i] = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(path[i]));
|
||||||
|
|
||||||
|
const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(actor);
|
||||||
|
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
||||||
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, flags);
|
||||||
|
constexpr float endTolerance = 0;
|
||||||
|
mPathFinder.buildPath(actor, start, randomAllowedPosition, pathgridGraph, agentBounds, flags, areaCosts,
|
||||||
|
endTolerance, PathType::Full, checkpoints);
|
||||||
|
|
||||||
|
if (!mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDestination = randomAllowedPosition;
|
||||||
|
mHasDestination = true;
|
||||||
|
mUsePathgrid = true;
|
||||||
|
// Remove this position as an option and add back the previously used position (stops NPC from picking the
|
||||||
|
// same position):
|
||||||
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||||
|
// check if mCurrentPosition was taken out of mAllowedPositions
|
||||||
|
if (storage.mTrimCurrentPosition && storage.mAllowedPositions.size() > 1)
|
||||||
|
storage.mTrimCurrentPosition = false;
|
||||||
else
|
else
|
||||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
storage.mAllowedPositions.push_back(storage.mCurrentPosition);
|
||||||
}
|
storage.mCurrentPosition = randomAllowedPosition;
|
||||||
|
|
||||||
void AiWander::trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder)
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
{
|
|
||||||
// TODO: how to add these back in once the door opens?
|
|
||||||
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
|
||||||
// Every now and then check whether one of the doors is opened. (maybe
|
|
||||||
// at the end of playing idle?) If the door is opened then re-calculate
|
|
||||||
// allowed nodes starting from the spawn point.
|
|
||||||
auto paths = pathfinder.getPath();
|
|
||||||
while (paths.size() >= 2)
|
|
||||||
{
|
|
||||||
const auto pt = paths.back();
|
|
||||||
for (unsigned int j = 0; j < nodes.size(); j++)
|
|
||||||
{
|
|
||||||
// FIXME: doesn't handle a door with the same X/Y
|
|
||||||
// coordinates but with a different Z
|
|
||||||
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
|
|
||||||
{
|
|
||||||
nodes.erase(nodes.begin() + j);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paths.pop_back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
||||||
|
|
@ -742,20 +759,20 @@ namespace MWMechanics
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
||||||
if (storage.mPopulateAvailableNodes)
|
if (storage.mPopulateAvailablePositions)
|
||||||
getAllowedNodes(actor, storage);
|
fillAllowedPositions(actor, storage);
|
||||||
|
|
||||||
if (storage.mAllowedNodes.empty())
|
if (storage.mAllowedPositions.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||||
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
int index = Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng);
|
||||||
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
|
const osg::Vec3f worldDest = storage.mAllowedPositions[index];
|
||||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
|
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
|
||||||
ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest);
|
osg::Vec3f dest = converter.toLocalVec3(worldDest);
|
||||||
|
|
||||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
const bool isPathGridOccupied
|
||||||
PathFinder::makeOsgVec3(worldDest), 60);
|
= MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(worldDest, 60);
|
||||||
|
|
||||||
// add offset only if the selected pathgrid is occupied by another actor
|
// add offset only if the selected pathgrid is occupied by another actor
|
||||||
if (isPathGridOccupied)
|
if (isPathGridOccupied)
|
||||||
|
|
@ -775,19 +792,17 @@ namespace MWMechanics
|
||||||
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
||||||
|
|
||||||
// add an offset towards random neighboring node
|
// add an offset towards random neighboring node
|
||||||
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
osg::Vec3f dir = Misc::Convert::makeOsgVec3f(connDest) - dest;
|
||||||
float length = dir.length();
|
const float length = dir.length();
|
||||||
dir.normalize();
|
dir.normalize();
|
||||||
|
|
||||||
for (int j = 1; j <= 3; j++)
|
for (int j = 1; j <= 3; j++)
|
||||||
{
|
{
|
||||||
// move for 5-15% towards random neighboring node
|
// move for 5-15% towards random neighboring node
|
||||||
dest
|
dest = dest + dir * (j * 5 * length / 100.f);
|
||||||
= PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
|
||||||
worldDest = converter.toWorldPoint(dest);
|
|
||||||
|
|
||||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||||
PathFinder::makeOsgVec3(worldDest), 60);
|
converter.toWorldVec3(dest), 60);
|
||||||
|
|
||||||
if (!isOccupied)
|
if (!isOccupied)
|
||||||
break;
|
break;
|
||||||
|
|
@ -807,19 +822,18 @@ namespace MWMechanics
|
||||||
|
|
||||||
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
|
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
|
||||||
// underground. Adding 20 in adjustPosition() is not enough.
|
// underground. Adding 20 in adjustPosition() is not enough.
|
||||||
dest.mZ += 60;
|
dest.z() += 60;
|
||||||
|
|
||||||
converter.toWorld(dest);
|
converter.toWorld(dest);
|
||||||
|
|
||||||
state.reset();
|
state.reset();
|
||||||
|
|
||||||
osg::Vec3f pos(static_cast<float>(dest.mX), static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
|
MWBase::Environment::get().getWorld()->moveObject(actor, dest);
|
||||||
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
|
|
||||||
actor.getClass().adjustPosition(actor, false);
|
actor.getClass().adjustPosition(actor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::getNeighbouringNodes(
|
void AiWander::getNeighbouringNodes(
|
||||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
||||||
{
|
{
|
||||||
const ESM::Pathgrid* pathgrid
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||||
|
|
@ -827,19 +841,19 @@ namespace MWMechanics
|
||||||
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
const size_t index = Misc::getClosestPoint(*pathgrid, dest);
|
||||||
|
|
||||||
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
void AiWander::fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
// infrequently used, therefore no benefit in caching it as a member
|
// infrequently used, therefore no benefit in caching it as a member
|
||||||
const MWWorld::CellStore* cellStore = actor.getCell();
|
const MWWorld::CellStore* cellStore = actor.getCell();
|
||||||
const ESM::Pathgrid* pathgrid
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
|
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
|
||||||
|
|
||||||
storage.mAllowedNodes.clear();
|
storage.mAllowedPositions.clear();
|
||||||
|
|
||||||
// If there is no path this actor doesn't go anywhere. See:
|
// If there is no path this actor doesn't go anywhere. See:
|
||||||
// https://forum.openmw.org/viewtopic.php?t=1556
|
// https://forum.openmw.org/viewtopic.php?t=1556
|
||||||
|
|
@ -860,34 +874,35 @@ namespace MWMechanics
|
||||||
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
||||||
|
|
||||||
// Find closest pathgrid point
|
// Find closest pathgrid point
|
||||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, npcPos);
|
||||||
|
|
||||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
// mAllowedPositions for this actor with pathgrid point indexes based on mDistance
|
||||||
// and if the point is connected to the closest current point
|
// and if the point is connected to the closest current point
|
||||||
// NOTE: mPoints is in local coordinates
|
// NOTE: mPoints is in local coordinates
|
||||||
size_t pointIndex = 0;
|
size_t pointIndex = 0;
|
||||||
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
||||||
{
|
{
|
||||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
|
const osg::Vec3f nodePos = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[counter]);
|
||||||
if ((npcPos - nodePos).length2() <= mDistance * mDistance
|
if ((npcPos - nodePos).length2() <= mDistance * mDistance
|
||||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
|
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
|
||||||
{
|
{
|
||||||
storage.mAllowedNodes.push_back(converter.toWorldPoint(pathgrid->mPoints[counter]));
|
storage.mAllowedPositions.push_back(
|
||||||
|
Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid->mPoints[counter])));
|
||||||
pointIndex = counter;
|
pointIndex = counter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (storage.mAllowedNodes.size() == 1)
|
if (storage.mAllowedPositions.size() == 1)
|
||||||
{
|
{
|
||||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(mInitialActorPosition));
|
storage.mAllowedPositions.push_back(mInitialActorPosition);
|
||||||
addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter);
|
addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter);
|
||||||
}
|
}
|
||||||
if (!storage.mAllowedNodes.empty())
|
if (!storage.mAllowedPositions.empty())
|
||||||
{
|
{
|
||||||
setCurrentNodeToClosestAllowedNode(storage);
|
setCurrentPositionToClosestAllowedPosition(storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.mPopulateAvailableNodes = false;
|
storage.mPopulateAvailablePositions = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When only one path grid point in wander distance,
|
// When only one path grid point in wander distance,
|
||||||
|
|
@ -901,44 +916,44 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if (edge.mV0 == pointIndex)
|
if (edge.mV0 == pointIndex)
|
||||||
{
|
{
|
||||||
AddPointBetweenPathGridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
addPositionBetweenPathgridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
||||||
converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage);
|
converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::AddPointBetweenPathGridPoints(
|
void AiWander::addPositionBetweenPathgridPoints(
|
||||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
osg::Vec3f vectorStart = Misc::Convert::makeOsgVec3f(start);
|
||||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
osg::Vec3f delta = Misc::Convert::makeOsgVec3f(end) - vectorStart;
|
||||||
float length = delta.length();
|
float length = delta.length();
|
||||||
delta.normalize();
|
delta.normalize();
|
||||||
|
|
||||||
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
|
unsigned distance = std::max(mDistance / 2, minimumWanderDistance);
|
||||||
|
|
||||||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||||
distance = std::min(distance, static_cast<int>(length));
|
distance = std::min(distance, static_cast<unsigned>(length));
|
||||||
delta *= distance;
|
delta *= distance;
|
||||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
|
storage.mAllowedPositions.push_back(vectorStart + delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage)
|
void AiWander::setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
float distanceToClosestNode = std::numeric_limits<float>::max();
|
float distanceToClosestPosition = std::numeric_limits<float>::max();
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for (size_t i = 0; i < storage.mAllowedNodes.size(); ++i)
|
for (size_t i = 0; i < storage.mAllowedPositions.size(); ++i)
|
||||||
{
|
{
|
||||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[i]));
|
const osg::Vec3f position = storage.mAllowedPositions[i];
|
||||||
float tempDist = (mInitialActorPosition - nodePos).length2();
|
const float tempDist = (mInitialActorPosition - position).length2();
|
||||||
if (tempDist < distanceToClosestNode)
|
if (tempDist < distanceToClosestPosition)
|
||||||
{
|
{
|
||||||
index = i;
|
index = i;
|
||||||
distanceToClosestNode = tempDist;
|
distanceToClosestPosition = tempDist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage.mCurrentNode = storage.mAllowedNodes[index];
|
storage.mCurrentPosition = storage.mAllowedPositions[index];
|
||||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index);
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||||
|
|
@ -970,8 +985,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
||||||
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
||||||
, mDistance(std::max(static_cast<short>(0), wander->mData.mDistance))
|
, mDistance(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDistance)))
|
||||||
, mDuration(std::max(static_cast<short>(0), wander->mData.mDuration))
|
, mDuration(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDuration)))
|
||||||
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
||||||
, mTimeOfDay(wander->mData.mTimeOfDay)
|
, mTimeOfDay(wander->mData.mTimeOfDay)
|
||||||
, mIdle(getInitialIdle(wander->mData.mIdle))
|
, mIdle(getInitialIdle(wander->mData.mIdle))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
||||||
#define GAME_MWMECHANICS_AIWANDER_H
|
#define GAME_MWMECHANICS_AIWANDER_H
|
||||||
|
|
||||||
#include "typedaipackage.hpp"
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "aitemporarybase.hpp"
|
#include "aitemporarybase.hpp"
|
||||||
#include "aitimer.hpp"
|
#include "aitimer.hpp"
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
|
#include "typedaipackage.hpp"
|
||||||
|
|
||||||
|
#include <components/esm3/loadpgrd.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
@ -51,14 +52,13 @@ namespace MWMechanics
|
||||||
unsigned short mIdleAnimation;
|
unsigned short mIdleAnimation;
|
||||||
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
||||||
|
|
||||||
// do we need to calculate allowed nodes based on mDistance
|
bool mPopulateAvailablePositions;
|
||||||
bool mPopulateAvailableNodes;
|
|
||||||
|
|
||||||
// allowed pathgrid nodes based on mDistance from the spawn point
|
// allowed destination positions based on mDistance from the spawn point
|
||||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
std::vector<osg::Vec3f> mAllowedPositions;
|
||||||
|
|
||||||
ESM::Pathgrid::Point mCurrentNode;
|
osg::Vec3f mCurrentPosition;
|
||||||
bool mTrimCurrentNode;
|
bool mTrimCurrentPosition;
|
||||||
|
|
||||||
float mCheckIdlePositionTimer;
|
float mCheckIdlePositionTimer;
|
||||||
int mStuckCount;
|
int mStuckCount;
|
||||||
|
|
@ -132,7 +132,8 @@ namespace MWMechanics
|
||||||
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||||
int getRandomIdle() const;
|
int getRandomIdle() const;
|
||||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
void setPathToAnAllowedPosition(
|
||||||
|
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
||||||
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
||||||
|
|
@ -145,28 +146,27 @@ namespace MWMechanics
|
||||||
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
||||||
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
||||||
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
bool isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
bool isNearAllowedPosition(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
||||||
|
|
||||||
const int mDistance; // how far the actor can wander from the spawn point
|
// how far the actor can wander from the spawn point
|
||||||
const int mDuration;
|
const unsigned mDistance;
|
||||||
|
const unsigned mDuration;
|
||||||
float mRemainingDuration;
|
float mRemainingDuration;
|
||||||
const int mTimeOfDay;
|
const int mTimeOfDay;
|
||||||
const std::vector<unsigned char> mIdle;
|
const std::vector<unsigned char> mIdle;
|
||||||
|
|
||||||
bool mStoredInitialActorPosition;
|
bool mStoredInitialActorPosition;
|
||||||
osg::Vec3f
|
// Note: an original engine does not reset coordinates even when actor changes a cell
|
||||||
mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
|
osg::Vec3f mInitialActorPosition;
|
||||||
|
|
||||||
bool mHasDestination;
|
bool mHasDestination;
|
||||||
osg::Vec3f mDestination;
|
osg::Vec3f mDestination;
|
||||||
bool mUsePathgrid;
|
bool mUsePathgrid;
|
||||||
|
|
||||||
void getNeighbouringNodes(
|
void getNeighbouringNodes(
|
||||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||||
|
|
||||||
void getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
void fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
|
|
||||||
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);
|
|
||||||
|
|
||||||
// constants for converting idleSelect values into groupNames
|
// constants for converting idleSelect values into groupNames
|
||||||
enum GroupIndex
|
enum GroupIndex
|
||||||
|
|
@ -175,12 +175,12 @@ namespace MWMechanics
|
||||||
GroupIndex_MaxIdle = 9
|
GroupIndex_MaxIdle = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage);
|
void setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage);
|
||||||
|
|
||||||
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
||||||
const Misc::CoordinateConverter& converter);
|
const Misc::CoordinateConverter& converter);
|
||||||
|
|
||||||
void AddPointBetweenPathGridPoints(
|
void addPositionBetweenPathgridPoints(
|
||||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
||||||
|
|
||||||
/// lookup table for converting idleSelect value to groupName
|
/// lookup table for converting idleSelect value to groupName
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "../mwbase/dialoguemanager.hpp"
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/luamanager.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
@ -240,8 +241,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue))
|
if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue))
|
||||||
{
|
{
|
||||||
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false,
|
MWBase::Environment::get().getLuaManager()->onHit(attacker, victim, weapon, projectile, 0,
|
||||||
MWMechanics::DamageSourceType::Ranged);
|
attackStrength, damage, false, hitPosition, false, MWMechanics::DamageSourceType::Ranged);
|
||||||
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
|
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -299,8 +300,8 @@ namespace MWMechanics
|
||||||
victim.getClass().getContainerStore(victim).add(projectile, 1);
|
victim.getClass().getContainerStore(victim).add(projectile, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
victim.getClass().onHit(
|
MWBase::Environment::get().getLuaManager()->onHit(attacker, victim, weapon, projectile, 0, attackStrength,
|
||||||
victim, damage, true, projectile, attacker, hitPosition, true, MWMechanics::DamageSourceType::Ranged);
|
damage, true, hitPosition, true, MWMechanics::DamageSourceType::Ranged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
enum GreetingState
|
enum class GreetingState
|
||||||
{
|
{
|
||||||
Greet_None,
|
None,
|
||||||
Greet_InProgress,
|
InProgress,
|
||||||
Greet_Done
|
Done
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1453,6 +1453,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
startCombat(actor, player, &playerFollowers);
|
startCombat(actor, player, &playerFollowers);
|
||||||
|
observerStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId());
|
||||||
|
|
||||||
// Apply aggression value to the base Fight rating, so that the actor can continue fighting
|
// Apply aggression value to the base Fight rating, so that the actor can continue fighting
|
||||||
// after a Calm spell wears off
|
// after a Calm spell wears off
|
||||||
|
|
|
||||||
|
|
@ -106,21 +106,6 @@ namespace MWMechanics
|
||||||
return visitor.mResult;
|
return visitor.mResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer,
|
|
||||||
std::vector<MWWorld::Ptr>* occupyingActors)
|
|
||||||
{
|
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
|
||||||
const osg::Vec3f halfExtents = world->getPathfindingAgentBounds(actor).mHalfExtents;
|
|
||||||
const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
|
||||||
if (ignorePlayer)
|
|
||||||
{
|
|
||||||
const std::array ignore{ actor, world->getPlayerConstPtr() };
|
|
||||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
|
||||||
}
|
|
||||||
const std::array ignore{ actor };
|
|
||||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
|
||||||
}
|
|
||||||
|
|
||||||
ObstacleCheck::ObstacleCheck()
|
ObstacleCheck::ObstacleCheck()
|
||||||
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
|
@ -24,9 +22,6 @@ namespace MWMechanics
|
||||||
/** \return Pointer to the door, or empty pointer if none exists **/
|
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||||
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination,
|
|
||||||
bool ignorePlayer = false, std::vector<MWWorld::Ptr>* occupyingActors = nullptr);
|
|
||||||
|
|
||||||
class ObstacleCheck
|
class ObstacleCheck
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <components/detournavigator/navigatorutils.hpp>
|
#include <components/detournavigator/navigatorutils.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
#include <components/misc/math.hpp>
|
#include <components/misc/math.hpp>
|
||||||
|
#include <components/misc/pathgridutils.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
@ -38,7 +39,7 @@ namespace
|
||||||
// points to a quadtree may help
|
// points to a quadtree may help
|
||||||
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
||||||
{
|
{
|
||||||
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
|
float potentialDistBetween = Misc::distanceSquared(grid->mPoints[counter], pos);
|
||||||
if (potentialDistBetween < closestDistanceReachable)
|
if (potentialDistBetween < closestDistanceReachable)
|
||||||
{
|
{
|
||||||
// found a closer one
|
// found a closer one
|
||||||
|
|
@ -197,7 +198,7 @@ namespace MWMechanics
|
||||||
// point right behind the wall that is closer than any pathgrid
|
// point right behind the wall that is closer than any pathgrid
|
||||||
// point outside the wall
|
// point outside the wall
|
||||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||||
size_t startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
const size_t startNode = Misc::getClosestPoint(*pathgrid, startPointInLocalCoords);
|
||||||
|
|
||||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||||
std::pair<size_t, bool> endNode
|
std::pair<size_t, bool> endNode
|
||||||
|
|
@ -206,8 +207,8 @@ namespace MWMechanics
|
||||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||||
// start or end to nearest pathgrid point, just travel from start to end.
|
// start or end to nearest pathgrid point, just travel from start to end.
|
||||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
float endTolastNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
float startTo1stNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||||
{
|
{
|
||||||
*out++ = endPoint;
|
*out++ = endPoint;
|
||||||
|
|
@ -223,7 +224,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||||
converter.toWorld(temp);
|
converter.toWorld(temp);
|
||||||
*out++ = makeOsgVec3(temp);
|
*out++ = Misc::Convert::makeOsgVec3f(temp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -234,8 +235,8 @@ namespace MWMechanics
|
||||||
if (path.size() > 1)
|
if (path.size() > 1)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
osg::Vec3f firstNodeVec3f = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[startNode]);
|
||||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
osg::Vec3f secondNodeVec3f = Misc::Convert::makeOsgVec3f(secondNode);
|
||||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||||
|
|
@ -259,7 +260,7 @@ namespace MWMechanics
|
||||||
// convert supplied path to world coordinates
|
// convert supplied path to world coordinates
|
||||||
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
||||||
converter.toWorld(point);
|
converter.toWorld(point);
|
||||||
return makeOsgVec3(point);
|
return Misc::Convert::makeOsgVec3f(point);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,26 +360,16 @@ namespace MWMechanics
|
||||||
mConstructed = true;
|
mConstructed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
|
||||||
{
|
|
||||||
mPath.clear();
|
|
||||||
mCell = cell;
|
|
||||||
|
|
||||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
|
||||||
|
|
||||||
mConstructed = !mPath.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType,
|
||||||
|
std::span<const osg::Vec3f> checkpoints)
|
||||||
{
|
{
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
|
|
||||||
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
|
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
|
||||||
DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags,
|
DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags,
|
||||||
areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
areaCosts, endTolerance, pathType, checkpoints, std::back_inserter(mPath));
|
||||||
|
|
||||||
if (status != DetourNavigator::Status::Success)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
|
|
@ -390,19 +381,19 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
PathType pathType, std::span<const osg::Vec3f> checkpoints)
|
||||||
{
|
{
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mCell = cell;
|
mCell = actor.getCell();
|
||||||
|
|
||||||
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
||||||
|
|
||||||
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
||||||
{
|
{
|
||||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance,
|
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance,
|
||||||
pathType, std::back_inserter(mPath));
|
pathType, checkpoints, std::back_inserter(mPath));
|
||||||
if (status != DetourNavigator::Status::Success)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +402,7 @@ namespace MWMechanics
|
||||||
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
||||||
{
|
{
|
||||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds,
|
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds,
|
||||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType,
|
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, checkpoints,
|
||||||
std::back_inserter(mPath));
|
std::back_inserter(mPath));
|
||||||
if (status != DetourNavigator::Status::Success)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
|
|
@ -429,12 +420,13 @@ namespace MWMechanics
|
||||||
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||||
|
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||||
{
|
{
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
const MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
const auto navigator = world->getNavigator();
|
const DetourNavigator::Navigator& navigator = *world.getNavigator();
|
||||||
const auto status = DetourNavigator::findPath(
|
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||||
*navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, out);
|
navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, checkpoints, out);
|
||||||
|
|
||||||
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
||||||
return DetourNavigator::Status::Success;
|
return DetourNavigator::Status::Success;
|
||||||
|
|
@ -451,9 +443,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
PathType pathType)
|
||||||
{
|
{
|
||||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||||
const auto maxDistance
|
const auto maxDistance
|
||||||
|
|
@ -461,9 +453,9 @@ namespace MWMechanics
|
||||||
const auto startToEnd = endPoint - startPoint;
|
const auto startToEnd = endPoint - startPoint;
|
||||||
const auto distance = startToEnd.length();
|
const auto distance = startToEnd.length();
|
||||||
if (distance <= maxDistance)
|
if (distance <= maxDistance)
|
||||||
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts,
|
return buildPath(
|
||||||
endTolerance, pathType);
|
actor, startPoint, endPoint, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||||
const auto end = startPoint + startToEnd * maxDistance / distance;
|
const auto end = startPoint + startToEnd * maxDistance / distance;
|
||||||
buildPath(actor, startPoint, end, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
buildPath(actor, startPoint, end, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <components/detournavigator/areatype.hpp>
|
#include <components/detournavigator/areatype.hpp>
|
||||||
#include <components/detournavigator/flags.hpp>
|
#include <components/detournavigator/flags.hpp>
|
||||||
#include <components/detournavigator/status.hpp>
|
#include <components/detournavigator/status.hpp>
|
||||||
#include <components/esm/position.hpp>
|
|
||||||
#include <components/esm3/loadpgrd.hpp>
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
|
|
@ -102,23 +103,20 @@ namespace MWMechanics
|
||||||
|
|
||||||
void buildStraightPath(const osg::Vec3f& endPoint);
|
void buildStraightPath(const osg::Vec3f& endPoint);
|
||||||
|
|
||||||
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
|
||||||
|
|
||||||
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
PathType pathType);
|
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||||
|
|
||||||
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||||
|
|
||||||
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
PathType pathType);
|
||||||
|
|
||||||
/// Remove front point if exist and within tolerance
|
/// Remove front point if exist and within tolerance
|
||||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||||
|
|
@ -145,61 +143,6 @@ namespace MWMechanics
|
||||||
mPath.push_back(point);
|
mPath.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
|
||||||
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
|
|
||||||
{
|
|
||||||
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
|
||||||
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
|
|
||||||
{
|
|
||||||
return ESM::Pathgrid::Point(
|
|
||||||
static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
|
|
||||||
{
|
|
||||||
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slightly cheaper version for comparisons.
|
|
||||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
|
||||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
|
||||||
//
|
|
||||||
static float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
|
||||||
{
|
|
||||||
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the closest pathgrid point index from the specified position
|
|
||||||
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
|
||||||
// (e.g. a cliff in front).
|
|
||||||
//
|
|
||||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
|
||||||
//
|
|
||||||
static size_t getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
|
||||||
{
|
|
||||||
assert(grid && !grid->mPoints.empty());
|
|
||||||
|
|
||||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
|
||||||
size_t closestIndex = 0;
|
|
||||||
|
|
||||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
|
||||||
// points to a quadtree may help
|
|
||||||
for (size_t counter = 1; counter < grid->mPoints.size(); counter++)
|
|
||||||
{
|
|
||||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
|
||||||
if (potentialDistBetween < distanceBetween)
|
|
||||||
{
|
|
||||||
distanceBetween = potentialDistBetween;
|
|
||||||
closestIndex = counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closestIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool mConstructed = false;
|
bool mConstructed = false;
|
||||||
std::deque<osg::Vec3f> mPath;
|
std::deque<osg::Vec3f> mPath;
|
||||||
|
|
@ -211,7 +154,8 @@ namespace MWMechanics
|
||||||
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||||
|
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -359,8 +359,7 @@ namespace
|
||||||
// Notify the target actor they've been hit
|
// Notify the target actor they've been hit
|
||||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||||
target.getClass().onHit(
|
target.getClass().onHit(target, {}, MWWorld::Ptr(), caster, true, MWMechanics::DamageSourceType::Magical);
|
||||||
target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true, MWMechanics::DamageSourceType::Magical);
|
|
||||||
// Apply resistances
|
// Apply resistances
|
||||||
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
||||||
{
|
{
|
||||||
|
|
@ -377,8 +376,11 @@ namespace
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||||
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
||||||
}
|
}
|
||||||
effect.mMinMagnitude *= magnitudeMult;
|
else if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||||
effect.mMaxMagnitude *= magnitudeMult;
|
{
|
||||||
|
effect.mMinMagnitude *= magnitudeMult;
|
||||||
|
effect.mMaxMagnitude *= magnitudeMult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return MWMechanics::MagicApplicationResult::Type::APPLIED;
|
return MWMechanics::MagicApplicationResult::Type::APPLIED;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
|
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
|
||||||
bool testAabbAgainstSphere(
|
inline bool testAabbAgainstSphere(
|
||||||
const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius)
|
const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius)
|
||||||
{
|
{
|
||||||
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||||
|
|
@ -18,35 +18,28 @@ namespace MWPhysics
|
||||||
return nearest.distance(position) < radius;
|
return nearest.distance(position) < radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Ignore, class OnCollision>
|
|
||||||
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group,
|
explicit HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask,
|
||||||
const Ignore& ignore, OnCollision* onCollision)
|
const int group, const btCollisionObject* ignore)
|
||||||
: mPosition(position)
|
: mPosition(position)
|
||||||
, mRadius(radius)
|
, mRadius(radius)
|
||||||
, mIgnore(ignore)
|
, mIgnore(ignore)
|
||||||
, mCollisionFilterMask(mask)
|
, mCollisionFilterMask(mask)
|
||||||
, mCollisionFilterGroup(group)
|
, mCollisionFilterGroup(group)
|
||||||
, mOnCollision(onCollision)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool process(const btBroadphaseProxy* proxy) override
|
bool process(const btBroadphaseProxy* proxy) override
|
||||||
{
|
{
|
||||||
if (mResult && mOnCollision == nullptr)
|
if (mResult)
|
||||||
return false;
|
return false;
|
||||||
const auto collisionObject = static_cast<btCollisionObject*>(proxy->m_clientObject);
|
const auto collisionObject = static_cast<btCollisionObject*>(proxy->m_clientObject);
|
||||||
if (mIgnore(collisionObject) || !needsCollision(*proxy)
|
if (mIgnore == collisionObject || !needsCollision(*proxy)
|
||||||
|| !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius))
|
|| !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius))
|
||||||
return true;
|
return true;
|
||||||
mResult = true;
|
mResult = true;
|
||||||
if (mOnCollision != nullptr)
|
|
||||||
{
|
|
||||||
(*mOnCollision)(collisionObject);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !mResult;
|
return !mResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,10 +48,9 @@ namespace MWPhysics
|
||||||
private:
|
private:
|
||||||
btVector3 mPosition;
|
btVector3 mPosition;
|
||||||
btScalar mRadius;
|
btScalar mRadius;
|
||||||
Ignore mIgnore;
|
const btCollisionObject* mIgnore;
|
||||||
int mCollisionFilterMask;
|
int mCollisionFilterMask;
|
||||||
int mCollisionFilterGroup;
|
int mCollisionFilterGroup;
|
||||||
OnCollision* mOnCollision;
|
|
||||||
bool mResult = false;
|
bool mResult = false;
|
||||||
|
|
||||||
bool needsCollision(const btBroadphaseProxy& proxy) const
|
bool needsCollision(const btBroadphaseProxy& proxy) const
|
||||||
|
|
|
||||||
|
|
@ -849,36 +849,18 @@ namespace MWPhysics
|
||||||
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool PhysicsSystem::isAreaOccupiedByOtherActor(
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, const float radius) const
|
||||||
{
|
{
|
||||||
std::vector<const btCollisionObject*> ignoredObjects;
|
const btCollisionObject* ignoredObject = nullptr;
|
||||||
ignoredObjects.reserve(ignore.size());
|
if (const auto it = mActors.find(actor); it != mActors.end())
|
||||||
for (const auto& v : ignore)
|
ignoredObject = it->second->getCollisionObject();
|
||||||
if (const auto it = mActors.find(v.mRef); it != mActors.end())
|
const btVector3 bulletPosition = Misc::Convert::toBullet(position);
|
||||||
ignoredObjects.push_back(it->second->getCollisionObject());
|
const btVector3 aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||||
std::sort(ignoredObjects.begin(), ignoredObjects.end());
|
const btVector3 aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||||
ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end());
|
|
||||||
const auto ignoreFilter = [&](const btCollisionObject* v) {
|
|
||||||
return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v);
|
|
||||||
};
|
|
||||||
const auto bulletPosition = Misc::Convert::toBullet(position);
|
|
||||||
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
|
||||||
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
|
||||||
const int mask = MWPhysics::CollisionType_Actor;
|
const int mask = MWPhysics::CollisionType_Actor;
|
||||||
const int group = MWPhysics::CollisionType_AnyPhysical;
|
const int group = MWPhysics::CollisionType_AnyPhysical;
|
||||||
if (occupyingActors == nullptr)
|
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoredObject);
|
||||||
{
|
|
||||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter,
|
|
||||||
static_cast<void (*)(const btCollisionObject*)>(nullptr));
|
|
||||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
|
||||||
return callback.getResult();
|
|
||||||
}
|
|
||||||
const auto onCollision = [&](const btCollisionObject* object) {
|
|
||||||
if (PtrHolder* holder = static_cast<PtrHolder*>(object->getUserPointer()))
|
|
||||||
occupyingActors->push_back(holder->getPtr());
|
|
||||||
};
|
|
||||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision);
|
|
||||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||||
return callback.getResult();
|
return callback.getResult();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,8 @@ namespace MWPhysics
|
||||||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool isAreaOccupiedByOtherActor(
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const;
|
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, float radius) const;
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||||
void reportCollision(const btVector3& position, const btVector3& normal);
|
void reportCollision(const btVector3& position, const btVector3& normal);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include "../mwmechanics/aitravel.hpp"
|
#include "../mwmechanics/aitravel.hpp"
|
||||||
#include "../mwmechanics/aiwander.hpp"
|
#include "../mwmechanics/aiwander.hpp"
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
#include "../mwmechanics/greetingstate.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
@ -487,7 +488,7 @@ namespace MWScript
|
||||||
else if (testedTargetId == "Player") // Currently the player ID is hardcoded
|
else if (testedTargetId == "Player") // Currently the player ID is hardcoded
|
||||||
{
|
{
|
||||||
MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager();
|
MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager();
|
||||||
bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress;
|
bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::GreetingState::InProgress;
|
||||||
bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor);
|
bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor);
|
||||||
targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor);
|
targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,8 @@ namespace MWWorld
|
||||||
throw std::runtime_error("class cannot hit");
|
throw std::runtime_error("class cannot hit");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker,
|
void Class::onHit(const Ptr& ptr, const std::map<std::string, float>& damages, const Ptr& object,
|
||||||
const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const
|
const Ptr& attacker, bool successful, const MWMechanics::DamageSourceType sourceType) const
|
||||||
{
|
{
|
||||||
throw std::runtime_error("class cannot be hit");
|
throw std::runtime_error("class cannot be hit");
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ namespace MWWorld
|
||||||
return std::make_pair(std::vector<int>(), false);
|
return std::make_pair(std::vector<int>(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::RefId Class::getEquipmentSkill(const ConstPtr& ptr) const
|
ESM::RefId Class::getEquipmentSkill(const ConstPtr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +235,7 @@ namespace MWWorld
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Class::getArmorRating(const MWWorld::Ptr& ptr) const
|
float Class::getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Class does not support armor rating");
|
throw std::runtime_error("Class does not support armor rating");
|
||||||
}
|
}
|
||||||
|
|
@ -452,11 +452,6 @@ namespace MWWorld
|
||||||
throw std::runtime_error("class does not support skills");
|
throw std::runtime_error("class does not support skills");
|
||||||
}
|
}
|
||||||
|
|
||||||
int Class::getBloodTexture(const MWWorld::ConstPtr& ptr) const
|
|
||||||
{
|
|
||||||
throw std::runtime_error("class does not support gore");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Class::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {}
|
void Class::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {}
|
||||||
|
|
||||||
void Class::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {}
|
void Class::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {}
|
||||||
|
|
@ -514,7 +509,8 @@ namespace MWWorld
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Class::getEffectiveArmorRating(const ConstPtr& armor, const Ptr& actor) const
|
float Class::getSkillAdjustedArmorRating(
|
||||||
|
const ConstPtr& armor, const Ptr& actor, bool useLuaInterfaceIfAvailable) const
|
||||||
{
|
{
|
||||||
throw std::runtime_error("class does not support armor ratings");
|
throw std::runtime_error("class does not support armor ratings");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <osg/Quat>
|
|
||||||
#include <osg/Vec4f>
|
#include <osg/Vec4f>
|
||||||
|
|
||||||
#include "doorstate.hpp"
|
#include "doorstate.hpp"
|
||||||
|
|
@ -16,9 +15,13 @@
|
||||||
#include "../mwmechanics/damagesourcetype.hpp"
|
#include "../mwmechanics/damagesourcetype.hpp"
|
||||||
|
|
||||||
#include <components/esm/refid.hpp>
|
#include <components/esm/refid.hpp>
|
||||||
#include <components/esm3/loadskil.hpp>
|
|
||||||
#include <components/vfs/pathutil.hpp>
|
#include <components/vfs/pathutil.hpp>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Quat;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct ObjectState;
|
struct ObjectState;
|
||||||
|
|
@ -144,11 +147,11 @@ namespace MWWorld
|
||||||
/// enums. ignored for creature attacks.
|
/// enums. ignored for creature attacks.
|
||||||
/// (default implementation: throw an exception)
|
/// (default implementation: throw an exception)
|
||||||
|
|
||||||
virtual void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
virtual void onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages,
|
||||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
const MWWorld::Ptr& object, const MWWorld::Ptr& attacker, bool successful,
|
||||||
const MWMechanics::DamageSourceType sourceType) const;
|
const MWMechanics::DamageSourceType sourceType) const;
|
||||||
///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is
|
///< Alerts \a ptr that it's being hit for \a damages by \a object (sword, arrow, etc). \a attacker specifies
|
||||||
/// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the
|
///< the
|
||||||
/// actor responsible for the attack. \a successful specifies if the hit is
|
/// actor responsible for the attack. \a successful specifies if the hit is
|
||||||
/// successful or not. \a sourceType classifies the damage source.
|
/// successful or not. \a sourceType classifies the damage source.
|
||||||
|
|
||||||
|
|
@ -209,7 +212,7 @@ namespace MWWorld
|
||||||
///
|
///
|
||||||
/// Default implementation: return (empty vector, false).
|
/// Default implementation: return (empty vector, false).
|
||||||
|
|
||||||
virtual ESM::RefId getEquipmentSkill(const ConstPtr& ptr) const;
|
virtual ESM::RefId getEquipmentSkill(const ConstPtr& ptr, bool useLuaInterfaceIfAvailable = false) const;
|
||||||
/// Return the index of the skill this item corresponds to when equipped.
|
/// Return the index of the skill this item corresponds to when equipped.
|
||||||
/// (default implementation: return empty ref id)
|
/// (default implementation: return empty ref id)
|
||||||
|
|
||||||
|
|
@ -255,7 +258,7 @@ namespace MWWorld
|
||||||
virtual ESM::RefId getSoundIdFromSndGen(const Ptr& ptr, std::string_view type) const;
|
virtual ESM::RefId getSoundIdFromSndGen(const Ptr& ptr, std::string_view type) const;
|
||||||
///< Returns the sound ID for \a ptr of the given soundgen \a type.
|
///< Returns the sound ID for \a ptr of the given soundgen \a type.
|
||||||
|
|
||||||
virtual float getArmorRating(const MWWorld::Ptr& ptr) const;
|
virtual float getArmorRating(const MWWorld::Ptr& ptr, bool useLuaInterfaceIfAvailable = false) const;
|
||||||
///< @return combined armor rating of this actor
|
///< @return combined armor rating of this actor
|
||||||
|
|
||||||
virtual const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const;
|
virtual const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const;
|
||||||
|
|
@ -310,9 +313,6 @@ namespace MWWorld
|
||||||
virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; }
|
virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; }
|
||||||
///< Return whether this class of object can be activated with telekinesis
|
///< Return whether this class of object can be activated with telekinesis
|
||||||
|
|
||||||
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
|
|
||||||
virtual int getBloodTexture(const MWWorld::ConstPtr& ptr) const;
|
|
||||||
|
|
||||||
virtual Ptr copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const;
|
virtual Ptr copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const;
|
||||||
|
|
||||||
// Similar to `copyToCell`, but preserves RefNum and moves LuaScripts.
|
// Similar to `copyToCell`, but preserves RefNum and moves LuaScripts.
|
||||||
|
|
@ -375,7 +375,8 @@ namespace MWWorld
|
||||||
virtual int getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const;
|
virtual int getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const;
|
||||||
|
|
||||||
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
||||||
virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const;
|
virtual float getSkillAdjustedArmorRating(
|
||||||
|
const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor, bool useLuaInterfaceIfAvailable = false) const;
|
||||||
|
|
||||||
virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
|
virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -566,10 +566,10 @@ namespace MWWorld
|
||||||
std::vector<Ref> refs;
|
std::vector<Ref> refs;
|
||||||
std::set<ESM::RefId> keyIDs;
|
std::set<ESM::RefId> keyIDs;
|
||||||
std::vector<ESM::RefId> refIDs;
|
std::vector<ESM::RefId> refIDs;
|
||||||
Store<ESM::Cell> Cells = get<ESM::Cell>();
|
const Store<ESM::Cell>& cells = get<ESM::Cell>();
|
||||||
for (auto it = Cells.intBegin(); it != Cells.intEnd(); ++it)
|
for (auto it = cells.intBegin(); it != cells.intEnd(); ++it)
|
||||||
readRefs(*it, refs, refIDs, keyIDs, readers);
|
readRefs(*it, refs, refIDs, keyIDs, readers);
|
||||||
for (auto it = Cells.extBegin(); it != Cells.extEnd(); ++it)
|
for (auto it = cells.extBegin(); it != cells.extEnd(); ++it)
|
||||||
readRefs(*it, refs, refIDs, keyIDs, readers);
|
readRefs(*it, refs, refIDs, keyIDs, readers);
|
||||||
const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
||||||
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_)
|
||||||
{
|
{
|
||||||
if (actorIsNpc)
|
if (actorIsNpc)
|
||||||
{
|
{
|
||||||
if (testCls.getEffectiveArmorRating(test, actor) <= unarmoredRating)
|
if (testCls.getSkillAdjustedArmorRating(test, actor) <= unarmoredRating)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -463,8 +463,8 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_)
|
||||||
// For NPCs, compare armor rating; for creatures, compare condition
|
// For NPCs, compare armor rating; for creatures, compare condition
|
||||||
if (actorIsNpc)
|
if (actorIsNpc)
|
||||||
{
|
{
|
||||||
const float rating = testCls.getEffectiveArmorRating(test, actor);
|
const float rating = testCls.getSkillAdjustedArmorRating(test, actor);
|
||||||
const float oldRating = oldCls.getEffectiveArmorRating(old, actor);
|
const float oldRating = oldCls.getSkillAdjustedArmorRating(old, actor);
|
||||||
if (rating <= oldRating)
|
if (rating <= oldRating)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ namespace MWWorld
|
||||||
|
|
||||||
void setLocals(const ESM::Script& script);
|
void setLocals(const ESM::Script& script);
|
||||||
|
|
||||||
MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); }
|
MWLua::LocalScripts* getLuaScripts() const { return mLuaScripts.get(); }
|
||||||
void setLuaScripts(std::shared_ptr<MWLua::LocalScripts>&&);
|
void setLuaScripts(std::shared_ptr<MWLua::LocalScripts>&&);
|
||||||
|
|
||||||
/// This flag is only used for content stack loading and will not be stored in the savegame.
|
/// This flag is only used for content stack loading and will not be stored in the savegame.
|
||||||
|
|
|
||||||
|
|
@ -3706,24 +3706,6 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::spawnBloodEffect(const Ptr& ptr, const osg::Vec3f& worldPosition)
|
|
||||||
{
|
|
||||||
if (ptr == getPlayerPtr() && Settings::gui().mHitFader)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string_view texture
|
|
||||||
= Fallback::Map::getString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr)));
|
|
||||||
if (texture.empty())
|
|
||||||
texture = Fallback::Map::getString("Blood_Texture_0");
|
|
||||||
|
|
||||||
// [0, 2]
|
|
||||||
const int number = Misc::Rng::rollDice(3);
|
|
||||||
const VFS::Path::Normalized model = Misc::ResourceHelpers::correctMeshPath(
|
|
||||||
VFS::Path::Normalized(Fallback::Map::getString("Blood_Model_" + std::to_string(number))));
|
|
||||||
|
|
||||||
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
||||||
const osg::Vec3f& worldPos, float scale, bool isMagicVFX, bool useAmbientLight)
|
const osg::Vec3f& worldPos, float scale, bool isMagicVFX, bool useAmbientLight)
|
||||||
{
|
{
|
||||||
|
|
@ -3871,10 +3853,11 @@ namespace MWWorld
|
||||||
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool World::isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
|
||||||
{
|
{
|
||||||
return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors);
|
const osg::Vec3f halfExtents = getPathfindingAgentBounds(actor).mHalfExtents;
|
||||||
|
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||||
|
return mPhysics->isAreaOccupiedByOtherActor(actor.mRef, position, 2 * maxHalfExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||||
|
|
|
||||||
|
|
@ -608,9 +608,6 @@ namespace MWWorld
|
||||||
/// Spawn a random creature from a levelled list next to the player
|
/// Spawn a random creature from a levelled list next to the player
|
||||||
void spawnRandomCreature(const ESM::RefId& creatureList) override;
|
void spawnRandomCreature(const ESM::RefId& creatureList) override;
|
||||||
|
|
||||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
|
||||||
void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override;
|
|
||||||
|
|
||||||
void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
|
||||||
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true,
|
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true,
|
||||||
bool useAmbientLight = true) override;
|
bool useAmbientLight = true) override;
|
||||||
|
|
@ -664,8 +661,7 @@ namespace MWWorld
|
||||||
bool hasCollisionWithDoor(
|
bool hasCollisionWithDoor(
|
||||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
|
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const override;
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const override;
|
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,6 @@ namespace
|
||||||
Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg)
|
Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg)
|
||||||
: mCfgMgr(cfg)
|
: mCfgMgr(cfg)
|
||||||
{
|
{
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
// this needs calling once so Qt can see its stream operators, which it needs when dragging and dropping
|
|
||||||
// it's automatic with Qt 6
|
|
||||||
qRegisterMetaTypeStreamOperators<SettingValue>("Config::SettingValue");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::GameSettings::validatePaths()
|
void Config::GameSettings::validatePaths()
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
@ -64,6 +63,25 @@ namespace DetourNavigator
|
||||||
std::reference_wrapper<const RecastSettings> mSettings;
|
std::reference_wrapper<const RecastSettings> mSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class ToNavMeshCoordinatesSpan
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ToNavMeshCoordinatesSpan(std::span<T> span, const RecastSettings& settings)
|
||||||
|
: mSpan(span)
|
||||||
|
, mSettings(settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const noexcept { return mSpan.size(); }
|
||||||
|
|
||||||
|
T operator[](std::size_t i) const noexcept { return toNavMeshCoordinates(mSettings, mSpan[i]); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::span<T> mSpan;
|
||||||
|
const RecastSettings& mSettings;
|
||||||
|
};
|
||||||
|
|
||||||
inline std::optional<std::size_t> findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
|
inline std::optional<std::size_t> findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
|
||||||
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
|
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
|
||||||
std::span<dtPolyRef> pathBuffer)
|
std::span<dtPolyRef> pathBuffer)
|
||||||
|
|
@ -79,7 +97,7 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
|
|
||||||
Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end,
|
Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||||
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize,
|
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, bool skipFirst,
|
||||||
std::output_iterator<osg::Vec3f> auto& out)
|
std::output_iterator<osg::Vec3f> auto& out)
|
||||||
{
|
{
|
||||||
assert(polygonPathSize <= polygonPath.size());
|
assert(polygonPathSize <= polygonPath.size());
|
||||||
|
|
@ -95,7 +113,7 @@ namespace DetourNavigator
|
||||||
dtStatusFailed(status))
|
dtStatusFailed(status))
|
||||||
return Status::FindStraightPathFailed;
|
return Status::FindStraightPathFailed;
|
||||||
|
|
||||||
for (int i = 0; i < cornersCount; ++i)
|
for (int i = skipFirst ? 1 : 0; i < cornersCount; ++i)
|
||||||
*out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast<std::size_t>(i) * 3]);
|
*out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast<std::size_t>(i) * 3]);
|
||||||
|
|
||||||
return Status::Success;
|
return Status::Success;
|
||||||
|
|
@ -103,7 +121,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start,
|
Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start,
|
||||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings,
|
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings,
|
||||||
float endTolerance, std::output_iterator<osg::Vec3f> auto out)
|
float endTolerance, const ToNavMeshCoordinatesSpan<const osg::Vec3f>& checkpoints,
|
||||||
|
std::output_iterator<osg::Vec3f> auto out)
|
||||||
{
|
{
|
||||||
dtQueryFilter queryFilter;
|
dtQueryFilter queryFilter;
|
||||||
queryFilter.setIncludeFlags(includeFlags);
|
queryFilter.setIncludeFlags(includeFlags);
|
||||||
|
|
@ -131,29 +150,66 @@ namespace DetourNavigator
|
||||||
return Status::EndPolygonNotFound;
|
return Status::EndPolygonNotFound;
|
||||||
|
|
||||||
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
||||||
const auto polygonPathSize
|
std::span<dtPolyRef> polygonPathBuffer = polygonPath;
|
||||||
= findPolygonPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, polygonPath);
|
dtPolyRef currentRef = startRef;
|
||||||
|
osg::Vec3f currentNavMeshPos = startNavMeshPos;
|
||||||
|
bool skipFirst = false;
|
||||||
|
|
||||||
if (!polygonPathSize.has_value())
|
for (std::size_t i = 0; i < checkpoints.size(); ++i)
|
||||||
|
{
|
||||||
|
const osg::Vec3f checkpointPos = checkpoints[i];
|
||||||
|
osg::Vec3f checkpointNavMeshPos;
|
||||||
|
dtPolyRef checkpointRef;
|
||||||
|
if (const dtStatus status = navMeshQuery.findNearestPoly(checkpointPos.ptr(), polyHalfExtents.ptr(),
|
||||||
|
&queryFilter, &checkpointRef, checkpointNavMeshPos.ptr());
|
||||||
|
dtStatusFailed(status) || checkpointRef == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const std::optional<std::size_t> toCheckpointPathSize = findPolygonPath(navMeshQuery, currentRef,
|
||||||
|
checkpointRef, currentNavMeshPos, checkpointNavMeshPos, queryFilter, polygonPath);
|
||||||
|
|
||||||
|
if (!toCheckpointPathSize.has_value())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (*toCheckpointPathSize == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (polygonPath[*toCheckpointPathSize - 1] != checkpointRef)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, checkpointNavMeshPos,
|
||||||
|
polygonPath, *toCheckpointPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||||
|
|
||||||
|
if (smoothStatus != Status::Success)
|
||||||
|
return smoothStatus;
|
||||||
|
|
||||||
|
currentRef = checkpointRef;
|
||||||
|
currentNavMeshPos = checkpointNavMeshPos;
|
||||||
|
skipFirst = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<std::size_t> toEndPathSize = findPolygonPath(
|
||||||
|
navMeshQuery, currentRef, endRef, currentNavMeshPos, endNavMeshPos, queryFilter, polygonPathBuffer);
|
||||||
|
|
||||||
|
if (!toEndPathSize.has_value())
|
||||||
return Status::FindPathOverPolygonsFailed;
|
return Status::FindPathOverPolygonsFailed;
|
||||||
|
|
||||||
if (*polygonPathSize == 0)
|
if (*toEndPathSize == 0)
|
||||||
return Status::Success;
|
return currentRef == endRef ? Status::Success : Status::PartialPath;
|
||||||
|
|
||||||
osg::Vec3f targetNavMeshPos;
|
osg::Vec3f targetNavMeshPos;
|
||||||
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
||||||
polygonPath[*polygonPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
polygonPath[*toEndPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||||
dtStatusFailed(status))
|
dtStatusFailed(status))
|
||||||
return Status::TargetPolygonNotFound;
|
return Status::TargetPolygonNotFound;
|
||||||
|
|
||||||
const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef;
|
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, targetNavMeshPos, polygonPath,
|
||||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, startNavMeshPos, targetNavMeshPos, polygonPath,
|
*toEndPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||||
*polygonPathSize, settings.mMaxSmoothPathSize, out);
|
|
||||||
|
|
||||||
if (smoothStatus != Status::Success)
|
if (smoothStatus != Status::Success)
|
||||||
return smoothStatus;
|
return smoothStatus;
|
||||||
|
|
||||||
return partialPath ? Status::PartialPath : Status::Success;
|
return polygonPath[*toEndPathSize - 1] == endRef ? Status::Success : Status::PartialPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
|
|
@ -21,13 +22,13 @@ namespace DetourNavigator
|
||||||
* @param end path at given point.
|
* @param end path at given point.
|
||||||
* @param includeFlags setup allowed navmesh areas.
|
* @param includeFlags setup allowed navmesh areas.
|
||||||
* @param out the beginning of the destination range.
|
* @param out the beginning of the destination range.
|
||||||
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents
|
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents.
|
||||||
|
* @param checkpoints is a sequence of positions the path should go over if possible.
|
||||||
* @return Status.
|
* @return Status.
|
||||||
* Equal to out if no path is found.
|
|
||||||
*/
|
*/
|
||||||
inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start,
|
inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start,
|
||||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance,
|
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance,
|
||||||
std::output_iterator<osg::Vec3f> auto out)
|
std::span<const osg::Vec3f> checkpoints, std::output_iterator<osg::Vec3f> auto out)
|
||||||
{
|
{
|
||||||
const auto navMesh = navigator.getNavMesh(agentBounds);
|
const auto navMesh = navigator.getNavMesh(agentBounds);
|
||||||
if (navMesh == nullptr)
|
if (navMesh == nullptr)
|
||||||
|
|
@ -37,7 +38,8 @@ namespace DetourNavigator
|
||||||
const auto locked = navMesh->lock();
|
const auto locked = navMesh->lock();
|
||||||
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
||||||
toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags,
|
toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags,
|
||||||
areaCosts, settings.mDetour, endTolerance, outTransform);
|
areaCosts, settings.mDetour, endTolerance, ToNavMeshCoordinatesSpan(checkpoints, settings.mRecast),
|
||||||
|
outTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ namespace ESMTerrain
|
||||||
inline std::pair<std::size_t, std::size_t> toCellAndLocal(
|
inline std::pair<std::size_t, std::size_t> toCellAndLocal(
|
||||||
std::size_t begin, std::size_t global, std::size_t cellSize)
|
std::size_t begin, std::size_t global, std::size_t cellSize)
|
||||||
{
|
{
|
||||||
|
// NOLINTBEGIN(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||||
std::size_t cell = global / (cellSize - 1);
|
std::size_t cell = global / (cellSize - 1);
|
||||||
|
// NOLINTEND(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||||
std::size_t local = global & (cellSize - 2);
|
std::size_t local = global & (cellSize - 2);
|
||||||
if (global != begin && local == 0)
|
if (global != begin && local == 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,7 @@ namespace L10n
|
||||||
// Try to load OpenMW translations from resources folder first.
|
// Try to load OpenMW translations from resources folder first.
|
||||||
// If we loaded them, try to load Qt translations from both
|
// If we loaded them, try to load Qt translations from both
|
||||||
// resources folder and default translations folder as well.
|
// resources folder and default translations folder as well.
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
auto qtPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
auto qtPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
||||||
#else
|
|
||||||
auto qtPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
|
|
||||||
#endif
|
|
||||||
auto localPath = resourcesPath + "/translations";
|
auto localPath = resourcesPath + "/translations";
|
||||||
|
|
||||||
if (AppTranslator.load(QLocale::system(), appName, "_", localPath)
|
if (AppTranslator.load(QLocale::system(), appName, "_", localPath)
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,29 @@ namespace LuaUtil
|
||||||
virtual bool isActive() const { return false; }
|
virtual bool isActive() const { return false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Call a function on an interface.
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
std::optional<T> callInterface(std::string_view interfaceName, std::string_view identifier, const Args&... args)
|
||||||
|
{
|
||||||
|
std::optional<T> res = std::nullopt;
|
||||||
|
mLua.protectedCall([&](LuaUtil::LuaView& view) {
|
||||||
|
LoadedData& data = ensureLoaded();
|
||||||
|
auto I = data.mPublicInterfaces.get<sol::optional<sol::table>>(interfaceName);
|
||||||
|
if (I)
|
||||||
|
{
|
||||||
|
auto o = I->get_or<sol::object>(identifier, sol::nil);
|
||||||
|
if (o.is<sol::function>())
|
||||||
|
{
|
||||||
|
sol::object luaRes = o.as<sol::function>().call(args...);
|
||||||
|
if (luaRes.is<T>())
|
||||||
|
res = luaRes.as<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
struct Handler
|
struct Handler
|
||||||
{
|
{
|
||||||
int mScriptId;
|
int mScriptId;
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,18 @@ namespace Misc
|
||||||
point.y() -= static_cast<float>(mCellY);
|
point.y() -= static_cast<float>(mCellY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osg::Vec3f toWorldVec3(const osg::Vec3f& point) const
|
||||||
|
{
|
||||||
|
osg::Vec3f result = point;
|
||||||
|
toWorld(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
|
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
|
||||||
{
|
{
|
||||||
return osg::Vec3f(
|
osg::Vec3f result = point;
|
||||||
point.x() - static_cast<float>(mCellX), point.y() - static_cast<float>(mCellY), point.z());
|
toLocal(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
53
components/misc/pathgridutils.hpp
Normal file
53
components/misc/pathgridutils.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||||
|
#define OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||||
|
|
||||||
|
#include "convert.hpp"
|
||||||
|
|
||||||
|
#include <components/esm3/loadpgrd.hpp>
|
||||||
|
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Misc
|
||||||
|
{
|
||||||
|
// Slightly cheaper version for comparisons.
|
||||||
|
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||||
|
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||||
|
//
|
||||||
|
inline float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
||||||
|
{
|
||||||
|
return (Misc::Convert::makeOsgVec3f(point) - pos).length2();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the closest pathgrid point index from the specified position
|
||||||
|
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
||||||
|
// (e.g. a cliff in front).
|
||||||
|
//
|
||||||
|
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||||
|
//
|
||||||
|
inline std::size_t getClosestPoint(const ESM::Pathgrid& grid, const osg::Vec3f& pos)
|
||||||
|
{
|
||||||
|
if (grid.mPoints.empty())
|
||||||
|
throw std::invalid_argument("Pathgrid has no points");
|
||||||
|
|
||||||
|
float minDistance = distanceSquared(grid.mPoints[0], pos);
|
||||||
|
std::size_t closestIndex = 0;
|
||||||
|
|
||||||
|
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||||
|
// points to a quadtree may help
|
||||||
|
for (std::size_t i = 1; i < grid.mPoints.size(); ++i)
|
||||||
|
{
|
||||||
|
const float distance = distanceSquared(grid.mPoints[i], pos);
|
||||||
|
if (minDistance > distance)
|
||||||
|
{
|
||||||
|
minDistance = distance;
|
||||||
|
closestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue