1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-29 12:04:31 +00:00

Merge remote-tracking branch 'origin/master' into spellgc

This commit is contained in:
SkyHasACat 2025-08-02 15:08:25 -07:00
commit ae9c42e0a0
516 changed files with 43541 additions and 8884 deletions

View file

@ -1,6 +1,7 @@
Checks: >
-*,
portability-*,
-portability-template-virtual-member-function,
clang-analyzer-*,
-clang-analyzer-optin.*,
-clang-analyzer-cplusplus.NewDeleteLeaks,
@ -13,3 +14,7 @@ HeaderFilterRegex: '(apps|components)/'
CheckOptions:
- key: readability-identifier-naming.ConceptCase
value: CamelCase
- key: readability-identifier-naming.NamespaceCase
value: CamelCase
- key: readability-identifier-naming.NamespaceIgnoredRegexp
value: 'bpo|osg(DB|FX|Particle|Shadow|Viewer|Util)?'

View file

@ -27,14 +27,14 @@ variables:
.Ubuntu_Image:
tags:
- saas-linux-medium-amd64
image: ubuntu:22.04
image: ubuntu:24.04
rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
Ubuntu_GCC_preprocess:
extends: .Ubuntu_Image
cache:
key: Ubuntu_GCC_preprocess.ubuntu_22.04.v1
key: Ubuntu_GCC_preprocess.ubuntu_24.04.v1
paths:
- apt-cache/
- .cache/pip/
@ -43,9 +43,12 @@ Ubuntu_GCC_preprocess:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
before_script:
- 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:
- CI/ubuntu_gcc_preprocess.sh
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu:
extends: .Ubuntu_Image
@ -77,9 +80,9 @@ Ubuntu_GCC_preprocess:
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_esm_refid_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_benchmark; fi
- ccache -s
- ccache -svv
- 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 './{}'
- cd ..
- df -h
@ -94,12 +97,12 @@ Ubuntu_GCC_preprocess:
Coverity:
tags:
- saas-linux-medium-amd64
image: ubuntu:22.04
image: ubuntu:24.04
stage: build
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
cache:
key: Coverity.ubuntu_22.04.v1
key: Coverity.ubuntu_24.04.v1
paths:
- apt-cache/
- ccache/
@ -124,7 +127,7 @@ Coverity:
- cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache
# Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc)
- ccache -s
- ccache -svv
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
@ -138,7 +141,7 @@ Coverity:
Ubuntu_GCC:
extends: .Ubuntu
cache:
key: Ubuntu_GCC.ubuntu_22.04.v1
key: Ubuntu_GCC.ubuntu_24.04.v1
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables:
@ -151,7 +154,7 @@ Ubuntu_GCC:
Ubuntu_GCC_asan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_asan.ubuntu_22.04.v1
key: Ubuntu_GCC_asan.ubuntu_24.04.v1
variables:
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak
@ -164,7 +167,7 @@ Clang_Format:
extends: .Ubuntu_Image
stage: checks
cache:
key: Ubuntu_Clang_Format.ubuntu_22.04.v1
key: Ubuntu_Clang_Format.ubuntu_24.04.v1
paths:
- apt-cache/
variables:
@ -180,11 +183,11 @@ Lupdate:
extends: .Ubuntu_Image
stage: checks
cache:
key: Ubuntu_lupdate.ubuntu_22.04.v1
key: Ubuntu_lupdate.ubuntu_24.04.v1
paths:
- apt-cache/
variables:
LUPDATE: lupdate
LUPDATE: /usr/lib/qt6/bin/lupdate
before_script:
- CI/install_debian_deps.sh openmw-qt-translations
script:
@ -206,7 +209,7 @@ Teal:
Ubuntu_GCC_Debug:
extends: .Ubuntu
cache:
key: Ubuntu_GCC_Debug.ubuntu_22.04.v2
key: Ubuntu_GCC_Debug.ubuntu_24.04.v2
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables:
@ -222,7 +225,7 @@ Ubuntu_GCC_Debug:
Ubuntu_GCC_tests:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests.ubuntu_22.04.v1
key: Ubuntu_GCC_tests.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -236,7 +239,7 @@ Ubuntu_GCC_tests:
.Ubuntu_GCC_tests_Debug:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests_Debug.ubuntu_22.04.v1
key: Ubuntu_GCC_tests_Debug.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -252,7 +255,7 @@ Ubuntu_GCC_tests:
Ubuntu_GCC_tests_asan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests_asan.ubuntu_22.04.v1
key: Ubuntu_GCC_tests_asan.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -268,11 +271,14 @@ Ubuntu_GCC_tests_asan:
when: always
reports:
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
Ubuntu_GCC_tests_ubsan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests_ubsan.ubuntu_22.04.v1
key: Ubuntu_GCC_tests_ubsan.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -285,11 +291,14 @@ Ubuntu_GCC_tests_ubsan:
when: always
reports:
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu_GCC_tests_tsan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests_tsan.ubuntu_22.04.v1
key: Ubuntu_GCC_tests_tsan.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -307,11 +316,15 @@ Ubuntu_GCC_tests_ubsan:
Ubuntu_GCC_tests_coverage:
extends: .Ubuntu_GCC_tests_Debug
cache:
key: Ubuntu_GCC_tests_coverage.ubuntu_22.04.v1
key: Ubuntu_GCC_tests_coverage.ubuntu_24.04.v1
paths:
- .cache/pip
variables:
BUILD_WITH_CODE_COVERAGE: 1
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage
- pipx install gcovr
coverage: /^\s*lines:\s*\d+.\d+\%/
artifacts:
paths: []
@ -322,6 +335,9 @@ Ubuntu_GCC_tests_coverage:
coverage_format: cobertura
path: coverage.xml
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu_Static_Deps:
extends: Ubuntu_Clang
@ -333,7 +349,7 @@ Ubuntu_GCC_tests_coverage:
- "CI/**/*"
- ".gitlab-ci.yml"
cache:
key: Ubuntu_Static_Deps.ubuntu_22.04.v1
key: Ubuntu_Static_Deps.ubuntu_24.04.v1
paths:
- apt-cache/
- ccache/
@ -350,7 +366,7 @@ Ubuntu_GCC_tests_coverage:
.Ubuntu_Static_Deps_tests:
extends: .Ubuntu_Static_Deps
cache:
key: Ubuntu_Static_Deps_tests.ubuntu_22.04.v1
key: Ubuntu_Static_Deps_tests.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -369,7 +385,7 @@ Ubuntu_Clang:
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
cache:
key: Ubuntu_Clang.ubuntu_22.04.v2
key: Ubuntu_Clang.ubuntu_24.04.v2
variables:
CC: clang
CXX: clang++
@ -382,7 +398,7 @@ Ubuntu_Clang:
before_script:
- CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic
cache:
key: Ubuntu_Clang_Tidy.ubuntu_22.04.v1
key: Ubuntu_Clang_Tidy.ubuntu_24.04.v1
variables:
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
@ -396,12 +412,15 @@ Ubuntu_Clang:
- cd build
- find . -name *.o -exec touch {} \;
- cmake --build . -- -j $(nproc) ${BUILD_TARGETS}
- ccache -s
- ccache -svv
artifacts:
paths:
- build/
expire_in: 12h
timeout: 3h
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
Ubuntu_Clang_Tidy_components:
extends: .Ubuntu_Clang_Tidy_Base
@ -414,7 +433,7 @@ Ubuntu_Clang_Tidy_openmw:
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: openmw
BUILD_TARGETS: openmw openmw-tests
timeout: 3h
Ubuntu_Clang_Tidy_openmw-cs:
@ -430,13 +449,13 @@ Ubuntu_Clang_Tidy_other:
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest components-tests openmw-tests openmw-cs-tests openmw-navmeshtool openmw-bulletobjecttool
BUILD_TARGETS: components-tests bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest openmw-navmeshtool openmw-bulletobjecttool
timeout: 3h
.Ubuntu_Clang_tests:
extends: Ubuntu_Clang
cache:
key: Ubuntu_Clang_tests.ubuntu_22.04.v1
key: Ubuntu_Clang_tests.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -450,7 +469,7 @@ Ubuntu_Clang_Tidy_other:
Ubuntu_Clang_tests_Debug:
extends: Ubuntu_Clang
cache:
key: Ubuntu_Clang_tests_Debug.ubuntu_22.04.v1
key: Ubuntu_Clang_tests_Debug.ubuntu_24.04.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
@ -474,7 +493,7 @@ Ubuntu_Clang_tests_Debug:
- apt-cache/
before_script:
- 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:
- CI/run_integration_tests.sh
after_script:
@ -485,7 +504,7 @@ Ubuntu_Clang_integration_tests:
needs:
- Ubuntu_Clang
cache:
key: Ubuntu_Clang_integration_tests.ubuntu_22.04.v2
key: Ubuntu_Clang_integration_tests.ubuntu_24.04.v2
variables:
OPENMW_DEPS: openmw-integration-tests
@ -494,15 +513,22 @@ Ubuntu_GCC_integration_tests_asan:
needs:
- Ubuntu_GCC_asan
cache:
key: Ubuntu_GCC_integration_tests_asan.ubuntu_22.04.v1
key: Ubuntu_GCC_integration_tests_asan.ubuntu_24.04.v1
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
.MacOS:
stage: build
rules:
- if: $CI_PROJECT_ID == "7107382"
- if: $CI_PROJECT_ID != "7107382"
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule"
image: macos-15-xcode-16
tags:
- saas-macos-medium-m1
cache:
paths:
- ccache/
@ -535,28 +561,28 @@ Ubuntu_GCC_integration_tests_asan:
paths:
- build/OpenMW-*.dmg
macOS14_Xcode15_amd64:
macOS15_Xcode16_amd64:
extends: .MacOS
image: macos-14-xcode-15
tags:
- saas-macos-medium-m1
cache:
key: macOS14_Xcode15_amd64.v2
key: macOS15_Xcode16_amd64.v1
variables:
CCACHE_SIZE: 3G
DMG_IDENTIFIER: amd64
MACOS_AMD64: true
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_EMOJI: true
HOMEBREW_NO_INSTALL_CLEANUP: true
macOS14_Xcode15_arm64:
macOS15_Xcode16_arm64:
extends: .MacOS
image: macos-14-xcode-15
tags:
- saas-macos-medium-m1
cache:
key: macOS14_Xcode15_arm64.v1
key: macOS15_Xcode16_arm64.v1
variables:
DMG_IDENTIFIER: arm64
CCACHE_SIZE: 3G
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_EMOJI: true
HOMEBREW_NO_INSTALL_CLEANUP: true
.Compress_And_Upload_Symbols_Base:
extends: .Ubuntu_Image
@ -681,7 +707,7 @@ macOS14_Xcode15_arm64:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-2022-v12
key: ninja-2022-v13
paths:
- ccache
- deps
@ -839,7 +865,7 @@ macOS14_Xcode15_arm64:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-2022-v12
key: msbuild-2022-v13
paths:
- deps
- MSVC2022_64/deps/Qt
@ -940,7 +966,7 @@ Windows_MSBuild_CacheInit:
- cd build
- cmake --build . -- -j $(nproc)
# - cmake --install . # no one uses builds anyway, disable until 'no space left' is resolved
- ccache -s
- ccache -svv
- df -h
- ls | grep -v -e '^extern$' -e '^install$' | xargs -I '{}' rm -rf './{}'
- cd ..
@ -968,7 +994,7 @@ Windows_MSBuild_CacheInit:
paths:
- .cache/pip
before_script:
- pip3 install --user requests click discord_webhook
- pip3 install --user --break-system-packages requests click discord_webhook
script:
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt

View file

@ -10,7 +10,8 @@ If you feel your name is missing from this list, please add it to `AUTHORS.md`.
Programmers
-----------
Bret Curtis (psi29a) - Project leader 2019-present
Alexey Dobrokhotov (Capo) - Project leader 2025-present
Bret Curtis (psi29a) - Project leader 2019-2025
Marc Zinnschlag (Zini) - Project leader 2010-2018
Nicolay Korslund - Project leader 2008-2010
scrawl - Top contributor
@ -49,7 +50,6 @@ Programmers
Berulacks
Bo Svensson
Britt Mathis (galdor557)
Capostrophic
Carl Maxwell
cc9cii
Cédric Mocquillon
@ -197,7 +197,7 @@ Programmers
Qlonever
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
Randy Davin (Kindi)
Randy Davin (Kuyondo)
rdimesio
rexelion
riothamus

View file

@ -1,7 +1,80 @@
0.50.0
------
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 #6039: Next Spell keybind fails while selected enchanted item has multiple copies
Bug #6573: Editor: Selection behaves incorrectly on high-DPI displays
Bug #6792: Birth sign info box has no line breaks
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 #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 #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 #7979: Paralyzed NPCs battlecry
Bug #8245: The console command ShowVars does not list global mwscripts
Bug #8265: Topics are linked incorrectly
Bug #8303: On target spells cast by non-actors should fire underwater
Bug #8318: Missing global variables are not handled gracefully in dialogue conditions
Bug #8333: Quest status subrecords should not actually cause parsing to skip remaining data
Bug #8340: Multi-effect enchantments are too expensive
Bug #8341: Repeat shader visitor passes discard parallax
Bug #8349: Travel to non-existent cell causes persistent black screen
Bug #8359: Some quick keys menu related issues
Bug #8371: Silence affects powers
Bug #8375: Moon phase cycle doesn't match Morrowind
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 #8404: Prevent merchant equipping breaks on lights
Bug #8408: OpenMW doesn't report all the potential resting hindrances
Bug #8414: Waterwalking works when collision is disabled
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 #8433: Wandering NPCs are not capable of avoiding easy obstacles
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 #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 #8466: Showmap "" reveals nameless cells
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 #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 #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 #8587: Minor INI importer problems
Bug #8593: Render targets do not generate mipmaps
Bug #8598: Post processing shaders don't interact with the vfs correctly
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 #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 #8614: Lua garbage collection fails to remove unused data
Bug #8615: Rest/wait time progress speed is different from vanilla
Feature #2522: Support quick item transfer
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 #8113: Support extended selection in autodetected subdirectory dialog
Feature #8139: Editor: Redesign the selection markers
Feature #8285: Expose list of active shaders in postprocessing API
Feature #8313: Show the character name in the savegame details
Feature #8320: Add access mwscript source text to lua api
Feature #8334: Lua: AddTopic equivalent
Feature #8355: Lua: Window visibility checking in interfaces.UI
Feature #8509: FillJournal script instruction
Feature #8580: Sort characters in the save loading menu
Feature #8597: Lua: Add more built-in event handlers
Feature #8629: Expose path grid data to Lua
0.49.0
------

View file

@ -1,9 +1,5 @@
#!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_AUTOREMOVE=1
if [[ "${MACOS_AMD64}" ]]; then
./CI/macos/before_install.amd64.sh
else

View file

@ -377,6 +377,8 @@ case $VS_VERSION in
MSVC_DISPLAY_YEAR="2022"
QT_MSVC_YEAR="2019"
VCPKG_TRIPLET="x64-windows"
;;
16|16.0|2019 )
@ -386,6 +388,8 @@ case $VS_VERSION in
MSVC_DISPLAY_YEAR="2019"
QT_MSVC_YEAR="2019"
VCPKG_TRIPLET="x64-windows-2019"
;;
15|15.0|2017 )
@ -546,7 +550,7 @@ fi
QT_VER='6.6.3'
AQT_VERSION='v3.1.15'
VCPKG_TAG="2024-11-10"
VCPKG_TAG="2025-07-23"
VCPKG_PATH="vcpkg-x64-${VS_VERSION:?}-${VCPKG_TAG:?}"
VCPKG_PDB_PATH="vcpkg-x64-${VS_VERSION:?}-pdb-${VCPKG_TAG:?}"
VCPKG_MANIFEST="${VCPKG_PATH:?}.txt"
@ -633,16 +637,16 @@ printf "vcpkg packages ${VCPKG_TAG:?}... "
fi
add_cmake_opts -DCMAKE_TOOLCHAIN_FILE="$(real_pwd)/${VCPKG_PATH:?}/scripts/buildsystems/vcpkg.cmake"
add_cmake_opts -DLuaJit_INCLUDE_DIR="$(real_pwd)/${VCPKG_PATH:?}/installed/x64-windows/include/luajit"
add_cmake_opts -DLuaJit_LIBRARY="$(real_pwd)/${VCPKG_PATH:?}/installed/x64-windows/lib/lua51.lib"
add_cmake_opts -DLuaJit_INCLUDE_DIR="$(real_pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/include/luajit"
add_cmake_opts -DLuaJit_LIBRARY="$(real_pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/lib/lua51.lib"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [[ ${CONFIGURATION:?} == "Debug" ]]; then
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/x64-windows/debug/bin"
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/debug/bin"
add_runtime_dlls ${CONFIGURATION:?} "${VCPKG_DLL_BIN:?}/Debug/MyGUIEngine_d.dll"
else
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/x64-windows/bin"
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/bin"
add_runtime_dlls ${CONFIGURATION:?} "${VCPKG_DLL_BIN:?}/Release/MyGUIEngine.dll"
fi
@ -704,17 +708,12 @@ printf "Qt ${QT_VER}... "
DLLSUFFIX=""
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"
if [ "${QT_MINOR_VER}" -ge 7 ]; then
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
else
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
fi
# Since Qt 6.7.0 plugin is called "qmodernwindowsstyle"
if [ "${QT_MINOR_VER}" -ge 7 ]; then
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
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"
fi

View file

@ -1 +1 @@
VCPKG_DEPS_TAG=2024-11-10
VCPKG_DEPS_TAG=2025-07-23

View file

@ -33,10 +33,10 @@ declare -rA GROUPED_DEPS=(
libboost-system-dev libboost-iostreams-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
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.
@ -57,22 +57,22 @@ declare -rA GROUPED_DEPS=(
libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev
"
[openmw-coverage]="gcovr"
[openmw-coverage]="pipx"
[openmw-integration-tests]="
ca-certificates
gdb
git
git-lfs
libavcodec58
libavformat58
libavutil56
libboost-iostreams1.74.0
libboost-program-options1.74.0
libboost-system1.74.0
libavcodec60
libavformat60
libavutil58
libboost-iostreams1.83.0
libboost-program-options1.83.0
libboost-system1.83.0
libbullet3.24
libcollada-dom2.5-dp0
libicu70
libicu74
libjpeg8
libluajit-5.1-2
liblz4-1
@ -80,19 +80,19 @@ declare -rA GROUPED_DEPS=(
libopenal1
libopenscenegraph161
libpng16-16
libqt5opengl5
libqt6opengl6
librecast1
libsdl2-2.0-0
libsqlite3-0
libswresample3
libswscale5
libswresample4
libswscale7
libtinyxml2.6.2v5
libyaml-cpp0.8
python3-pip
xvfb
"
[libasan6]="libasan6"
[libasan]="libasan8"
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
@ -102,8 +102,8 @@ declare -rA GROUPED_DEPS=(
"
[openmw-qt-translations]="
qttools5-dev
qttools5-dev-tools
qt6-tools-dev
qt6-tools-dev-tools
git-core
"
)

View file

@ -3,14 +3,7 @@
brew tap --repair
brew update --quiet
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd
command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt@6
# Install deps
brew install openal-soft icu4c yaml-cpp sqlite
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd ccache cmake qt@6 openal-soft icu4c yaml-cpp sqlite
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz
tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null

View file

@ -1,7 +1,7 @@
#!/bin/sh -ex
if [[ "${MACOS_AMD64}" ]]; then
arch -x86_64 ccache -s
arch -x86_64 ccache -svv
else
ccache -s
ccache -svv
fi

View file

@ -82,8 +82,8 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 50)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 78)
set(OPENMW_POSTPROCESSING_API_REVISION 2)
set(OPENMW_LUA_API_REVISION 87)
set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "")
@ -249,12 +249,8 @@ endif()
find_package(LZ4 REQUIRED)
if (USE_QT)
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5)
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()
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6)
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED)
message(STATUS "Using Qt${QT_VERSION}")
endif()
@ -466,7 +462,7 @@ find_package(Boost 1.70.0 CONFIG REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONA
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.4.3 REQUIRED)
endif()
find_package(SDL2 2.0.10 REQUIRED)
find_package(SDL2 2.0.20 REQUIRED)
find_package(OpenAL REQUIRED)
find_package(ZLIB REQUIRED)
@ -590,30 +586,10 @@ if(OPENMW_LTO_BUILD)
endif()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(OPENMW_CXX_FLAGS "-Wall -Wextra -Wundef -Wextra-semi -Wno-unused-parameter -pedantic -Wno-long-long -Wnon-virtual-dtor -Wunused ${OPENMW_CXX_FLAGS}")
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438
set(OPENMW_CXX_FLAGS "-Wno-array-bounds ${OPENMW_CXX_FLAGS}")
endif()
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6)
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-potentially-evaluated-expression")
endif ()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-unused-but-set-parameter -Wduplicated-branches -Wduplicated-cond -Wlogical-op")
endif()
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
endif()
# Extern
@ -624,8 +600,42 @@ if (BUILD_OPENCS OR BUILD_OPENCS_TESTS)
add_subdirectory (extern/osgQt)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
add_compile_options("/W4")
set(WARNINGS_DISABLE
4100 # Unreferenced formal parameter (-Wunused-parameter)
4127 # Conditional expression is constant
4996 # Function was declared deprecated
5054 # Deprecated operations between enumerations of different types caused by Qt headers
)
foreach(d ${WARNINGS_DISABLE})
add_compile_options("/wd${d}")
endforeach(d)
if(OPENMW_MSVC_WERROR)
add_compile_options("/WX")
endif()
else ()
add_compile_options("-Wall" "-Wextra" "-Wundef" "-Wextra-semi" "-Wno-unused-parameter" "-pedantic" "-Wno-long-long" "-Wnon-virtual-dtor" "-Wunused")
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6)
add_compile_options("-Wno-potentially-evaluated-expression")
endif ()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
add_compile_options("-Wno-unused-but-set-parameter" "-Wduplicated-branches" "-Wduplicated-cond" "-Wlogical-op")
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438
add_compile_options("-Wno-array-bounds")
endif()
endif ()
if (OPENMW_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}")
separate_arguments(OPENMW_CXX_FLAGS NATIVE_COMMAND "${OPENMW_CXX_FLAGS}")
add_compile_options(${OPENMW_CXX_FLAGS})
endif()
# Components
@ -715,87 +725,9 @@ if (WIN32)
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# Play a bit with the warning levels
set(WARNINGS "/W4")
set(WARNINGS_DISABLE
4100 # Unreferenced formal parameter (-Wunused-parameter)
4127 # Conditional expression is constant
4996 # Function was declared deprecated
5054 # Deprecated operations between enumerations of different types caused by Qt headers
)
foreach(d ${WARNINGS_DISABLE})
list(APPEND WARNINGS "/wd${d}")
endforeach(d)
if(OPENMW_MSVC_WERROR)
list(APPEND WARNINGS "/WX")
endif()
target_compile_options(components PRIVATE ${WARNINGS})
target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS})
if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920)
target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE)
endif()
if (BUILD_BSATOOL)
target_compile_options(bsatool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESMTOOL)
target_compile_options(esmtool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESSIMPORTER)
target_compile_options(openmw-essimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_LAUNCHER)
target_compile_options(openmw-launcher PRIVATE ${WARNINGS})
endif()
if (BUILD_MWINIIMPORTER)
target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENCS)
target_compile_options(openmw-cs PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENMW)
target_compile_options(openmw PRIVATE ${WARNINGS})
endif()
if (BUILD_WIZARD)
target_compile_options(openmw-wizard PRIVATE ${WARNINGS})
endif()
if (BUILD_COMPONENTS_TESTS)
target_compile_options(components-tests PRIVATE ${WARNINGS})
endif()
if (BUILD_BENCHMARKS)
target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS})
endif()
if (BUILD_NAVMESHTOOL)
target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS})
endif()
if (BUILD_BULLETOBJECTTOOL)
target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD})
endif()
if (BUILD_OPENCS_TESTS)
target_compile_options(openmw-cs-tests PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENMW_TESTS)
target_compile_options(openmw-tests PRIVATE ${WARNINGS})
endif()
endif(MSVC)
# TODO: At some point release builds should not use the console but rather write to a log file

View file

@ -155,7 +155,7 @@ namespace
VFS::Manager vfs;
VFS::registerArchives(&vfs, fileCollections, archives, true);
VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder());
Settings::Manager::load(config);

View file

@ -139,7 +139,7 @@ namespace
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);
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)
{
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);
}
@ -156,7 +156,7 @@ namespace
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
ASSERT_TRUE(mNavigator->addAgent(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);
}
@ -172,7 +172,7 @@ namespace
updateGuard.reset();
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);
EXPECT_THAT(mPath,
@ -194,7 +194,7 @@ namespace
updateGuard.reset();
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);
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
@ -218,7 +218,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -237,7 +237,7 @@ namespace
mPath.clear();
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);
EXPECT_THAT(mPath,
@ -265,7 +265,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -285,7 +285,7 @@ namespace
mPath.clear();
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);
EXPECT_THAT(mPath,
@ -318,7 +318,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -386,7 +386,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -421,7 +421,7 @@ namespace
mEnd.x() = 256;
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);
EXPECT_THAT(mPath,
@ -453,8 +453,8 @@ namespace
mStart.x() = 256;
mEnd.x() = 256;
EXPECT_EQ(
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
{}, mOut),
Status::Success);
EXPECT_THAT(mPath,
@ -487,8 +487,8 @@ namespace
mStart.x() = 256;
mEnd.x() = 256;
EXPECT_EQ(
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
{}, mOut),
Status::Success);
EXPECT_THAT(mPath,
@ -520,7 +520,7 @@ namespace
mStart.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);
EXPECT_THAT(mPath,
@ -549,7 +549,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -577,7 +577,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -658,7 +658,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -781,7 +781,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -806,7 +806,7 @@ namespace
mNavigator->update(mPlayerPosition, nullptr);
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);
EXPECT_THAT(mPath,
@ -834,7 +834,7 @@ namespace
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);
EXPECT_THAT(mPath,
@ -979,6 +979,146 @@ namespace
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>
{
};

View file

@ -723,7 +723,7 @@ namespace ESM
TEST_P(Esm3SaveLoadRecordTest, landShouldNotChange)
{
LandRecordData data;
std::iota(data.mHeights.begin(), data.mHeights.end(), 1);
std::iota(data.mHeights.begin(), data.mHeights.end(), 1.0f);
std::for_each(data.mHeights.begin(), data.mHeights.end(), [](float& v) { v *= Land::sHeightScale; });
data.mMinHeight = *std::min_element(data.mHeights.begin(), data.mHeights.end());
data.mMaxHeight = *std::max_element(data.mHeights.begin(), data.mHeights.end());

View file

@ -5,7 +5,7 @@
namespace
{
using namespace testing;
using namespace fx::Lexer;
using namespace Fx::Lexer;
struct LexerTest : Test
{

View file

@ -91,7 +91,7 @@ namespace
)" };
using namespace testing;
using namespace fx;
using namespace Fx;
struct TechniqueTest : Test
{
@ -113,7 +113,8 @@ namespace
void compile(const std::string& name)
{
mTechnique = std::make_unique<Technique>(*mVFS.get(), mImageManager, name, 1, 1, true, true);
mTechnique = std::make_unique<Technique>(
*mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true);
mTechnique->compile();
}
};

View file

@ -24,6 +24,8 @@ namespace
constexpr VFS::Path::NormalizedView test2EnPath("l10n/test2/en.yaml");
constexpr VFS::Path::NormalizedView test3EnPath("l10n/test3/en.yaml");
constexpr VFS::Path::NormalizedView test3DePath("l10n/test3/de.yaml");
constexpr VFS::Path::NormalizedView test4RuPath("l10n/test4/ru.yaml");
constexpr VFS::Path::NormalizedView test4EnPath("l10n/test4/en.yaml");
VFSTestFile invalidScript("not a script");
VFSTestFile incorrectScript(
@ -69,6 +71,16 @@ currency: "You have {money, number, currency}"
VFSTestFile test2En(R"X(
good_morning: "Morning!"
you_have_arrows: "Arrows count: {count}"
)X");
VFSTestFile test4Ru(R"X(
skill_increase: "Ваш навык {навык} увеличился до {value}"
acrobatics: "Акробатика"
)X");
VFSTestFile test4En(R"X(
stat_increase: "Your {stat} has increased to {value}"
speed: "Speed"
)X");
struct LuaL10nTest : Test
@ -80,6 +92,8 @@ you_have_arrows: "Arrows count: {count}"
{ test2EnPath, &test2En },
{ test3EnPath, &test1En },
{ test3DePath, &test1De },
{ test4RuPath, &test4Ru },
{ test4EnPath, &test4En },
});
LuaUtil::ScriptsConfiguration mCfg;
@ -91,7 +105,7 @@ you_have_arrows: "Arrows count: {count}"
lua.protectedCall([&](LuaUtil::LuaView& view) {
sol::state_view& l = view.sol();
internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get());
L10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" });
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: gmst de en\n");
@ -169,6 +183,18 @@ you_have_arrows: "Arrows count: {count}"
l.safe_script("t3 = l10n('Test3', 'de')");
l10nManager.setPreferredLocales({ "en" });
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
// Test that formatting arguments use a correct encoding
l.safe_script("t4 = l10n('Test4', 'ru')");
l10nManager.setPreferredLocales({ "ru", "en" });
EXPECT_EQ(get<std::string>(l, "t4('skill_increase', {навык='Акробатика', value=100})"),
"Ваш навык Акробатика увеличился до 100");
EXPECT_EQ(get<std::string>(l, "t4('skill_increase', {навык=t4('acrobatics'), value=100})"),
"Ваш навык Акробатика увеличился до 100");
EXPECT_EQ(get<std::string>(l, "t4('stat_increase', {stat='Speed', value=100})"),
"Your Speed has increased to 100");
EXPECT_EQ(get<std::string>(l, "t4('stat_increase', {stat=t4('speed'), value=100})"),
"Your Speed has increased to 100");
});
}
}

View file

@ -638,8 +638,9 @@ CUSTOM: customdata.lua
sol::object deserialized = LuaUtil::deserialize(lua.sol(), data2.mScripts[0].mData, &serializer1);
EXPECT_TRUE(deserialized.is<sol::table>());
sol::table table = deserialized;
for (const auto& [key, value] : table)
if (!table.empty())
{
const auto [key, value] = *table.cbegin();
EXPECT_TRUE(key.is<ESM::RefNum>());
EXPECT_TRUE(value.is<ESM::RefNum>());
EXPECT_EQ(key.as<ESM::RefNum>(), (ESM::RefNum{ 42, 34 }));

View file

@ -9,22 +9,33 @@ namespace
{
using namespace testing;
struct LuaUtilPackageTest : Test
{
LuaUtil::LuaState mLuaState{ nullptr, nullptr };
LuaUtilPackageTest()
{
mLuaState.addInternalLibSearchPath(
std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "components" / "lua");
sol::state_view sol = mLuaState.unsafeState();
sol["util"] = LuaUtil::initUtilPackage(sol);
}
};
template <typename T>
T get(sol::state& lua, const std::string& luaCode)
T get(sol::state_view& lua, const std::string& luaCode)
{
return lua.safe_script("return " + luaCode).get<T>();
}
std::string getAsString(sol::state& lua, std::string luaCode)
std::string getAsString(sol::state_view& lua, std::string luaCode)
{
return LuaUtil::toString(lua.safe_script("return " + luaCode));
}
TEST(LuaUtilPackageTest, Vector2)
TEST_F(LuaUtilPackageTest, Vector2)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector2(3, 4)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 3);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 4);
@ -55,11 +66,9 @@ namespace
EXPECT_TRUE(get<bool>(lua, "swizzle['01'] == util.vector2(0, 1) and swizzle['0y'] == util.vector2(0, 2)"));
}
TEST(LuaUtilPackageTest, Vector3)
TEST_F(LuaUtilPackageTest, Vector3)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector3(5, 12, 13)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
@ -94,11 +103,9 @@ namespace
get<bool>(lua, "swizzle['001'] == util.vector3(0, 0, 1) and swizzle['0yx'] == util.vector3(0, 2, 1)"));
}
TEST(LuaUtilPackageTest, Vector4)
TEST_F(LuaUtilPackageTest, Vector4)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector4(5, 12, 13, 15)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
@ -136,11 +143,9 @@ namespace
lua, "swizzle['0001'] == util.vector4(0, 0, 0, 1) and swizzle['0yx1'] == util.vector4(0, 2, 1, 1)"));
}
TEST(LuaUtilPackageTest, Color)
TEST_F(LuaUtilPackageTest, Color)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)");
EXPECT_EQ(get<std::string>(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)");
lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)");
@ -155,11 +160,9 @@ namespace
EXPECT_TRUE(get<bool>(lua, "red:asRgb() == util.vector3(1, 0, 0)"));
}
TEST(LuaUtilPackageTest, Transform)
TEST_F(LuaUtilPackageTest, Transform)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua["T"] = lua["util"]["transform"];
lua["v"] = lua["util"]["vector3"];
EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index");
@ -191,11 +194,9 @@ namespace
EXPECT_LT(get<float>(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6);
}
TEST(LuaUtilPackageTest, UtilityFunctions)
TEST_F(LuaUtilPackageTest, UtilityFunctions)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), -0.5f);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 0.86602539f);
@ -203,6 +204,10 @@ namespace
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(0.1, 0, 1.5)"), 0.1f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(-0.1, 0, 1.5)"), 0);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(2.1, 0, 1.5)"), 1.5f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.round(2.1)"), 2.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.round(-2.1)"), -2.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.remap(5, 0, 10, 0, 100)"), 50.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.remap(-5, 0, 10, 0, 100)"), -50.0f);
lua.safe_script("t = util.makeReadOnly({x = 1})");
EXPECT_FLOAT_EQ(get<float>(lua, "t.x"), 1);
EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value");

View file

@ -26,6 +26,15 @@ namespace
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({});
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
auto correctESM4SoundPath = [](auto path, auto* vfs) {
return Misc::ResourceHelpers::correctResourcePath({ { "sound" } }, path, vfs, ".mp3");
};
EXPECT_EQ(correctESM4SoundPath("foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("SOUND/foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("DATA\\SOUND\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("\\Data/Sound\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
}
namespace

View file

@ -215,8 +215,6 @@ int main(int argc, char** argv)
std::cerr << "ERROR: " << e.what() << std::endl;
return 1;
}
return 0;
}
namespace

View file

@ -249,7 +249,7 @@ namespace ESSImport
{
ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory;
for (size_t i = 0; i < invState.mItems.size(); ++i)
for (uint32_t i = 0; i < static_cast<uint32_t>(invState.mItems.size()); ++i)
{
// FIXME: in case of conflict (multiple items with this refID) use the already equipped one?
if (invState.mItems[i].mRef.mRefID == refr.mActorData.mSelectedEnchantItem)

View file

@ -242,11 +242,36 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
{
if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen)
{
QString customSizeMessage = tr("Custom window size is available only in Windowed mode.");
QString windowBorderMessage = tr("Window border is available only in Windowed mode.");
standardRadioButton->toggle();
customRadioButton->setEnabled(false);
customWidthSpinBox->setEnabled(false);
customHeightSpinBox->setEnabled(false);
windowBorderCheckBox->setEnabled(false);
windowBorderCheckBox->setToolTip(windowBorderMessage);
customWidthSpinBox->setToolTip(customSizeMessage);
customHeightSpinBox->setToolTip(customSizeMessage);
customRadioButton->setToolTip(customSizeMessage);
}
if (mode == Settings::WindowMode::Fullscreen)
{
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
}
else if (mode == Settings::WindowMode::WindowedFullscreen)
{
QString fullScreenMessage = tr("Windowed Fullscreen mode always uses the native display resolution.");
resolutionComboBox->setEnabled(false);
resolutionComboBox->setToolTip(fullScreenMessage);
standardRadioButton->setToolTip(fullScreenMessage);
// Assume that a first item is a native screen resolution
resolutionComboBox->setCurrentIndex(0);
}
else
{
@ -254,6 +279,13 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
customWidthSpinBox->setEnabled(true);
customHeightSpinBox->setEnabled(true);
windowBorderCheckBox->setEnabled(true);
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
windowBorderCheckBox->setToolTip("");
customWidthSpinBox->setToolTip("");
customHeightSpinBox->setToolTip("");
customRadioButton->setToolTip("");
}
}

View file

@ -88,11 +88,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
// Create the file if it doesn't already exist, else the importer will fail
auto path = mCfgMgr.getUserConfigPath();
path /= "openmw.cfg";
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFile file(path);
#else
QFile file(Files::pathToQString(path));
#endif
if (!file.exists())
{

View file

@ -42,7 +42,7 @@ int runLauncher(int argc, char* argv[])
resourcesPath = Files::pathToQString(variables["resources"].as<Files::MaybeQuotedPath>().u8string());
}
l10n::installQtTranslations(app, "launcher", resourcesPath);
L10n::installQtTranslations(app, "launcher", resourcesPath);
Launcher::MainDialog mainWin(configurationManager);

View file

@ -497,11 +497,7 @@ bool Launcher::MainDialog::writeSettings()
}
// Game settings
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFile file(userPath / Files::openmwCfgFile);
#else
QFile file(Files::getUserConfigPathQString(mCfgMgr));
#endif
if (!file.open(QIODevice::ReadWrite | QIODevice::Text))
{

View file

@ -163,8 +163,6 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingInt(Settings::physics().mAsyncNumThreads, *physicsThreadsSpinBox);
loadSettingBool(
Settings::game().mAllowActorsToFollowOverWaterSurface, *allowNPCToFollowOverWaterSurfaceCheckBox);
loadSettingBool(
Settings::game().mUnarmedCreatureAttacksDamageArmor, *unarmedCreatureAttacksDamageArmorCheckBox);
loadSettingInt(Settings::game().mActorCollisionShapeType, *actorCollisonShapeTypeComboBox);
}
@ -373,8 +371,6 @@ void Launcher::SettingsPage::saveSettings()
saveSettingInt(*physicsThreadsSpinBox, Settings::physics().mAsyncNumThreads);
saveSettingBool(
*allowNPCToFollowOverWaterSurfaceCheckBox, Settings::game().mAllowActorsToFollowOverWaterSurface);
saveSettingBool(
*unarmedCreatureAttacksDamageArmorCheckBox, Settings::game().mUnarmedCreatureAttacksDamageArmor);
saveSettingInt(*actorCollisonShapeTypeComboBox, Settings::game().mActorCollisionShapeType);
}

View file

@ -53,7 +53,7 @@
</property>
</widget>
</item>
<item row="10" column="1">
<item row="9" column="1">
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Don't use race weight in NPC movement speed calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -63,7 +63,7 @@
</property>
</widget>
</item>
<item row="9" column="1">
<item row="8" column="1">
<widget class="QCheckBox" name="classicCalmSpellsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -73,7 +73,7 @@
</property>
</widget>
</item>
<item row="12" column="1">
<item row="11" column="1">
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -123,7 +123,7 @@
</property>
</widget>
</item>
<item row="6" column="1">
<item row="5" column="1">
<widget class="QCheckBox" name="requireAppropriateAmmunitionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -133,7 +133,7 @@
</property>
</widget>
</item>
<item row="13" column="1">
<item row="12" column="1">
<widget class="QCheckBox" name="graphicHerbalismCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -143,7 +143,7 @@
</property>
</widget>
</item>
<item row="11" column="1">
<item row="10" column="1">
<widget class="QCheckBox" name="swimUpwardCorrectionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -153,7 +153,7 @@
</property>
</widget>
</item>
<item row="8" column="1">
<item row="7" column="1">
<widget class="QCheckBox" name="enchantedWeaponsMagicalCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -183,7 +183,7 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="6" column="1">
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -203,7 +203,7 @@
</property>
</widget>
</item>
<item row="5" column="1">
<item row="4" column="1">
<widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Effects of reflected Absorb spells are not mirrored - like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -213,16 +213,6 @@
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="unarmedCreatureAttacksDamageArmorCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Unarmed Creature Attacks Damage Armor</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View file

@ -9,8 +9,6 @@
#include <fstream>
#include <iostream>
namespace sfs = std::filesystem;
namespace
{
// from configfileparser.cpp

View file

@ -10,7 +10,6 @@
#include <components/files/conversion.hpp>
namespace bpo = boost::program_options;
namespace sfs = std::filesystem;
#ifndef _WIN32
int main(int argc, char* argv[])

View file

@ -189,7 +189,7 @@ namespace NavMeshTool
VFS::Manager vfs;
VFS::registerArchives(&vfs, fileCollections, archives, true);
VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder());
Settings::Manager::load(config);

View file

@ -201,8 +201,8 @@ namespace NavMeshTool
{
if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition
|| (land->mDataTypes & ESM::Land::DATA_VHGT) == 0)
return { HeightfieldPlane{ ESM::Land::DEFAULT_HEIGHT }, ESM::Land::DEFAULT_HEIGHT,
ESM::Land::DEFAULT_HEIGHT };
return { HeightfieldPlane{ static_cast<float>(ESM::Land::DEFAULT_HEIGHT) },
static_cast<float>(ESM::Land::DEFAULT_HEIGHT), static_cast<float>(ESM::Land::DEFAULT_HEIGHT) };
ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique<ESM::Land::LandData>());
land->loadData(ESM::Land::DATA_VHGT, landData);

View file

@ -113,7 +113,7 @@ bool isBSA(const std::filesystem::path& path)
std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
{
if (isBSA(path))
return VFS::makeBsaArchive(path);
return VFS::makeBsaArchive(path, nullptr);
if (std::filesystem::is_directory(path))
return std::make_unique<VFS::FileSystemArchive>(path);
return nullptr;
@ -198,7 +198,7 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
{
try
{
readVFS(VFS::makeBsaArchive(file.second), file.second, quiet);
readVFS(VFS::makeBsaArchive(file.second, nullptr), file.second, quiet);
}
catch (const std::exception& e)
{

View file

@ -88,7 +88,7 @@ opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands objectmarker
)
opencs_units (view/render
@ -240,11 +240,7 @@ target_link_libraries(openmw-cs-lib
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)
else()
target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::Svg)
endif()
target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg)
if (WIN32)
target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest)

View file

@ -34,11 +34,11 @@ bool CSMFilter::TextNode::test(const CSMWorld::IdTableBase& table, int row, cons
QString string;
if (data.type() == QVariant::String)
if (data.typeId() == QMetaType::QString)
{
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)))
{
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()))
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";
}

View file

@ -29,8 +29,8 @@ bool CSMFilter::ValueNode::test(const CSMWorld::IdTableBase& table, int row, con
QVariant data = table.data(index);
if (data.type() != QVariant::Double && data.type() != QVariant::Bool && data.type() != QVariant::Int
&& data.type() != QVariant::UInt && data.type() != static_cast<QVariant::Type>(QMetaType::Float))
if (data.typeId() != QMetaType::Double && data.typeId() != QMetaType::Bool && data.typeId() != QMetaType::Int
&& data.typeId() != QMetaType::UInt && data.typeId() != QMetaType::Float)
return false;
double value = data.toDouble();

View file

@ -62,39 +62,31 @@ namespace CSMPrefs
{
QWidget* widget = static_cast<QWidget*>(watched);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
unsigned int mod = (unsigned int)keyEvent->modifiers();
unsigned int key = (unsigned int)keyEvent->key();
if (!keyEvent->isAutoRepeat())
return activate(widget, mod, key);
return activate(widget, keyEvent->keyCombination());
}
else if (event->type() == QEvent::KeyRelease)
{
QWidget* widget = static_cast<QWidget*>(watched);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
unsigned int mod = (unsigned int)keyEvent->modifiers();
unsigned int key = (unsigned int)keyEvent->key();
if (!keyEvent->isAutoRepeat())
return deactivate(widget, mod, key);
return deactivate(widget, keyEvent->keyCombination());
}
else if (event->type() == QEvent::MouseButtonPress)
{
QWidget* widget = static_cast<QWidget*>(watched);
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)
{
QWidget* widget = static_cast<QWidget*>(watched);
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)
{
@ -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;
bool used = false;
@ -167,7 +159,7 @@ namespace CSMPrefs
if (!shortcut->isEnabled())
continue;
if (checkModifier(mod, button, shortcut, true))
if (checkModifier(keyCombination, shortcut, true))
used = true;
if (shortcut->getActivationStatus() != Shortcut::AS_Inactive)
@ -175,7 +167,8 @@ namespace CSMPrefs
int pos = shortcut->getPosition();
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)
{
@ -220,10 +213,8 @@ namespace CSMPrefs
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;
while (widget)
@ -235,11 +226,11 @@ namespace CSMPrefs
{
Shortcut* shortcut = *it;
if (checkModifier(mod, button, shortcut, false))
if (checkModifier(keyCombination, shortcut, false))
used = true;
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)
{
@ -268,13 +259,13 @@ namespace CSMPrefs
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
|| shortcut->getModifierStatus() == activate)
return false;
MatchResult result = match(mod, button, shortcut->getModifier());
MatchResult result = match(keyCombination.keyboardModifiers(), keyCombination.key(), shortcut->getModifier());
bool used = false;
if (result != Matches_Not)

View file

@ -42,11 +42,11 @@ namespace CSMPrefs
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);

View file

@ -115,15 +115,12 @@ namespace CSMPrefs
std::string ShortcutManager::convertToString(const QKeySequence& sequence) const
{
const int MouseKeyMask = 0x01FFFFFF;
const int ModMask = 0x7E000000;
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 key = sequence[i] & MouseKeyMask;
int mods = sequence[i].keyboardModifiers();
int key = sequence[i].key();
if (key)
{

View file

@ -59,13 +59,6 @@ void CSMPrefs::State::declare()
.setTooltip("Minimum width of subviews.")
.setRange(50, 10000);
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");
declareEnum(mValues->mRecords.mStatusFormat, "Modification Status Display Format");
@ -180,7 +173,10 @@ void CSMPrefs::State::declare()
declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic Projection Size Parameter")
.setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.")
.setRange(10, 10000);
declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1);
declareDouble(mValues->mRendering.mObjectMarkerScale, "Object Marker Scale Factor")
.setPrecision(2)
.setRange(.01f, 100.f)
.setTooltip("Multiplier for the size of object selection markers.");
declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background");
declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour");
declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour")
@ -376,6 +372,7 @@ void CSMPrefs::State::declare()
declareShortcut(mValues->mKeyBindings.mSceneScaleSubmode, "Scale Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneRotateSubmode, "Rotate Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneCameraCycle, "Cycle Camera Mode");
declareShortcut(mValues->mKeyBindings.mSceneToggleMarker, "Toggle Selection Marker");
declareSubcategory("1st/Free Camera");
declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward");

View file

@ -258,7 +258,7 @@ namespace CSMPrefs
Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 };
Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false };
Settings::SettingValue<int> mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 };
Settings::SettingValue<double> mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 };
Settings::SettingValue<double> mObjectMarkerScale{ mIndex, sName, "object-marker-scale", 5.0 };
Settings::SettingValue<bool> mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true };
Settings::SettingValue<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour",
"#6e7880" };
@ -491,7 +491,7 @@ namespace CSMPrefs
Settings::SettingValue<std::string> mSceneScaleSubmode{ mIndex, sName, "scene-submode-scale", "V" };
Settings::SettingValue<std::string> mSceneRotateSubmode{ mIndex, sName, "scene-submode-rotate", "R" };
Settings::SettingValue<std::string> mSceneCameraCycle{ mIndex, sName, "scene-cam-cycle", "Tab" };
Settings::SettingValue<std::string> mSceneToggleMarkers{ mIndex, sName, "scene-toggle-markers", "F4" };
Settings::SettingValue<std::string> mSceneToggleMarker{ mIndex, sName, "scene-toggle-marker", "F4" };
Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" };
Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };
@ -507,8 +507,10 @@ namespace CSMPrefs
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> mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" };
Settings::SettingValue<std::string> mScriptEditorComment{ mIndex, sName, "script-editor-comment", "" };
Settings::SettingValue<std::string> mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment", "" };
Settings::SettingValue<std::string> mScriptEditorComment{ mIndex, sName, "script-editor-comment",
"Ctrl+Slash" };
Settings::SettingValue<std::string> mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment",
"Ctrl+Shift+Question" };
};
struct ModelsCategory : Settings::WithIndex

View file

@ -143,7 +143,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
, mArchives(archives)
, mVFS(std::make_unique<VFS::Manager>())
{
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true);
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder());
mResourcesManager.setVFS(mVFS.get());
@ -1465,7 +1465,7 @@ std::vector<ESM::RefId> CSMWorld::Data::getIds(bool listDeleted) const
void CSMWorld::Data::assetsChanged()
{
mVFS.get()->reset();
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true);
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder());
const UniversalId assetTableIds[] = { UniversalId::Type_Meshes, UniversalId::Type_Icons, UniversalId::Type_Musics,
UniversalId::Type_SoundsRes, UniversalId::Type_Textures, UniversalId::Type_Videos };

View file

@ -625,8 +625,6 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(
default:
throw std::logic_error("InfoCondition: operator can not be used to compare");
}
return false;
}
template <typename T1, typename T2>
@ -651,8 +649,6 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(
default:
throw std::logic_error("InfoCondition: operator can not be used to compare");
}
return false;
}
QVariant CSMWorld::ConstInfoSelectWrapper::getValue() const

View file

@ -753,7 +753,6 @@ void CSMWorld::RefIdCollection::cloneRecord(
bool CSMWorld::RefIdCollection::touchRecord(const ESM::RefId& id)
{
throw std::runtime_error("RefIdCollection::touchRecord is unimplemented");
return false;
}
void CSMWorld::RefIdCollection::appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type)

View file

@ -659,11 +659,7 @@ void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string
//
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);
#endif
mSubViewWindow.addDockWidget(Qt::TopDockWidgetArea, view);

View file

@ -50,7 +50,7 @@ namespace CSVFilter
std::pair<std::string, FilterType> operator()(const QVariant& variantData)
{
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)
filterType = FilterType::String;
if (dataType == QMetaType::Int || dataType == QMetaType::Float)

View file

@ -25,9 +25,10 @@
#include "cellwater.hpp"
#include "instancedragmodes.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "objectmarker.hpp"
#include "pathgrid.hpp"
#include "terrainstorage.hpp"
#include "worldspacewidget.hpp"
#include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp>
@ -107,9 +108,6 @@ bool CSVRender::Cell::addObjects(int start, int end)
auto object = std::make_unique<Object>(mData, mCellNode, id, false);
if (mSubModeElementMask & Mask_Reference)
object->setSubMode(mSubMode);
mObjects.insert(std::make_pair(id, object.release()));
modified = true;
}
@ -168,9 +166,10 @@ void CSVRender::Cell::unloadLand()
mCellBorder.reset();
}
CSVRender::Cell::Cell(
CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior)
: mData(document.getData())
CSVRender::Cell::Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode,
const std::string& id, bool deleted, bool isExterior)
: mSelectionMarker(selectionMarker)
, mData(document.getData())
, mId(ESM::RefId::stringRefId(id))
, mDeleted(deleted)
, mSubMode(0)
@ -466,7 +465,10 @@ void CSVRender::Cell::setSelection(int elementMask, Selection mode)
}
iter->second->setSelected(selected);
if (selected)
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
mSelectionMarker->updateSelectionMarker();
}
if (mPathgrid && elementMask & Mask_Pathgrid)
{
@ -506,8 +508,10 @@ void CSVRender::Cell::selectAllWithSameParentId(int elementMask)
if (!iter->second->getSelected() && ids.find(iter->second->getReferenceableId()) != ids.end())
{
iter->second->setSelected(true);
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
@ -520,6 +524,9 @@ void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
else if (dragMode == DragMode_Select_Invert)
object->setSelected(!object->getSelected());
if (object->getSelected())
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
}
void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
@ -542,6 +549,8 @@ void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3
}
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
@ -555,6 +564,8 @@ void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distan
if (distanceFromObject < distance)
handleSelectDrag(object.second, dragMode);
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::setCellArrows(int mask)
@ -625,9 +636,11 @@ void CSVRender::Cell::selectFromGroup(const std::vector<std::string>& group)
if (objectName == object->getReferenceId())
{
object->setSelected(true, osg::Vec4f(1, 0, 1, 1));
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
}
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::unhideAll()
@ -673,8 +686,7 @@ void CSVRender::Cell::setSubMode(int subMode, unsigned int elementMask)
mSubModeElementMask = elementMask;
if (elementMask & Mask_Reference)
for (std::map<std::string, Object*>::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter)
iter->second->setSubMode(subMode);
mSelectionMarker->setSubMode(subMode);
}
void CSVRender::Cell::reset(unsigned int elementMask)
@ -685,3 +697,11 @@ void CSVRender::Cell::reset(unsigned int elementMask)
if (mPathgrid && elementMask & Mask_Pathgrid)
mPathgrid->resetIndicators();
}
CSVRender::Object* CSVRender::Cell::getObjectByReferenceId(const std::string& referenceId)
{
if (auto iter = mObjects.find(Misc::StringUtils::lowerCase(referenceId)); iter != mObjects.end())
return iter->second;
else
return nullptr;
}

View file

@ -9,9 +9,9 @@
#include <osg/Vec3d>
#include <osg/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp"
#include "worldspacewidget.hpp"
#include <components/esm/refid.hpp>
#include <components/misc/algorithm.hpp>
@ -44,8 +44,11 @@ namespace CSVRender
class CellBorder;
class CellMarker;
class ObjectMarker;
class Cell
{
ObjectMarker* const mSelectionMarker;
CSMWorld::Data& mData;
ESM::RefId mId;
osg::ref_ptr<osg::Group> mCellNode;
@ -90,8 +93,8 @@ namespace CSVRender
public:
/// \note Deleted covers both cells that are deleted and cells that don't exist in
/// the first place.
Cell(CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted = false,
bool isExterior = false);
Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode, const std::string& id,
bool deleted = false, bool isExterior = false);
~Cell();
@ -182,6 +185,8 @@ namespace CSVRender
/// true state.
void reset(unsigned int elementMask);
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId);
friend class CellNodeCallback;
};
}

View file

@ -362,7 +362,29 @@ CSVRender::InstanceMode::InstanceMode(
for (const char axis : "xyz")
connect(new CSMPrefs::Shortcut(std::string("scene-axis-") + axis, worldspaceWidget),
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] { this->setDragAxis(axis); });
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] {
this->setDragAxis(axis);
std::string axisStr(1, toupper(axis));
switch (getSubMode())
{
case (Object::Mode_Move):
axisStr += "_Axis";
break;
case (Object::Mode_Rotate):
axisStr += "_Axis_Rot";
break;
case (Object::Mode_Scale):
axisStr += "_Axis_Scale";
break;
}
auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (mDragAxis != -1)
selectionMarker->updateMarkerHighlight(axisStr, axis - 'x');
else
selectionMarker->resetMarkerHighlight();
});
}
void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar)
@ -460,52 +482,58 @@ void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hi
void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
getWorldspaceWidget().clearSelection(Mask_Reference);
auto& worldspaceWidget = getWorldspaceWidget();
if (hit.tag)
worldspaceWidget.clearSelection(Mask_Reference);
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, select it
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
return;
}
// hit an Object, select it
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.tag)
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected());
const auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (object->getSelected())
selectionMarker->addToSelectionHistory(object->getReferenceId(), false);
selectionMarker->updateSelectionMarker();
}
}
void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit)
{
auto* snapTarget = dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get());
if (snapTarget)
if (auto* snapTarget
= dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()))
{
snapTarget->mObject->setSnapTarget(false);
}
if (hit.tag)
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
}
}
@ -514,23 +542,26 @@ bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask());
auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference);
WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
{
// Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -591,23 +622,26 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask());
auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference);
WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
{
// Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -641,10 +675,10 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
mDragMode = DragMode_Scale_Snap;
// Calculate scale factor
std::vector<osg::ref_ptr<TagBase>> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference);
std::vector<osg::ref_ptr<TagBase>> editedSelection = worldspaceWidget.getEdited(Mask_Reference);
osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection));
int widgetHeight = getWorldspaceWidget().height();
int widgetHeight = worldspaceWidget.height();
float dx = pos.x() - center.x();
float dy = (widgetHeight - pos.y()) - center.y();
@ -1098,7 +1132,7 @@ void CSVRender::InstanceMode::dropEvent(QDropEvent* event)
return;
WorldspaceHitResult hit
= getWorldspaceWidget().mousePick(event->pos(), getWorldspaceWidget().getInteractionMask());
= getWorldspaceWidget().mousePick(event->position().toPoint(), getWorldspaceWidget().getInteractionMask());
std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos);

View file

@ -18,25 +18,11 @@
#include <apps/opencs/model/world/universalid.hpp>
#include <apps/opencs/view/render/tagbase.hpp>
#include <osg/Array>
#include <osg/BoundingSphere>
#include <osg/GL>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Math>
#include <osg/Node>
#include <osg/PositionAttitudeTransform>
#include <osg/PrimitiveSet>
#include <osg/Quat>
#include <osg/Shape>
#include <osg/ShapeDrawable>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Vec3>
#include <osgFX/Scribe>
#include "../../model/prefs/state.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
@ -63,11 +49,6 @@ namespace ESM
struct Light;
}
const float CSVRender::Object::MarkerShaftWidth = 30;
const float CSVRender::Object::MarkerShaftBaseLength = 70;
const float CSVRender::Object::MarkerHeadWidth = 50;
const float CSVRender::Object::MarkerHeadLength = 50;
namespace
{
@ -95,12 +76,6 @@ QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHi
return QString::fromUtf8(mObject->getReferenceableId().c_str());
}
CSVRender::ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
void CSVRender::Object::clear() {}
void CSVRender::Object::update()
@ -204,238 +179,6 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
return mData.getReferences().getRecord(mReferenceId).get();
}
void CSVRender::Object::updateMarker()
{
for (int i = 0; i < 3; ++i)
{
if (mMarker[i])
{
mRootNode->removeChild(mMarker[i]);
mMarker[i] = osg::ref_ptr<osg::Node>();
}
if (mSelected)
{
if (mSubMode == 0)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 1)
{
mMarker[i] = makeRotateMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 2)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
}
}
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker(int axis)
{
osg::ref_ptr<osg::Geometry> geometry(new osg::Geometry);
float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius();
// shaft
osg::Vec3Array* vertices = new osg::Vec3Array;
for (int i = 0; i < 2; ++i)
{
float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
}
// head backside
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
// head
vertices->push_back(getMarkerPosition(0, 0, shaftLength + MarkerHeadLength, axis));
geometry->setVertexArray(vertices);
osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0);
// shaft
for (int i = 0; i < 4; ++i)
{
int i2 = i == 3 ? 0 : i + 1;
primitives->push_back(i);
primitives->push_back(4 + i);
primitives->push_back(i2);
primitives->push_back(4 + i);
primitives->push_back(4 + i2);
primitives->push_back(i2);
}
// cap
primitives->push_back(0);
primitives->push_back(1);
primitives->push_back(2);
primitives->push_back(2);
primitives->push_back(3);
primitives->push_back(0);
// head, backside
primitives->push_back(0 + 8);
primitives->push_back(1 + 8);
primitives->push_back(2 + 8);
primitives->push_back(2 + 8);
primitives->push_back(3 + 8);
primitives->push_back(0 + 8);
for (int i = 0; i < 4; ++i)
{
primitives->push_back(12);
primitives->push_back(8 + (i == 3 ? 0 : i + 1));
primitives->push_back(8 + i);
}
geometry->addPrimitiveSet(primitives);
osg::Vec4Array* colours = new osg::Vec4Array;
for (int i = 0; i < 8; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i = 8; i < 8 + 4 + 1; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.0f, axis == 1 ? 1.0f : 0.0f, axis == 2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group(new osg::Group);
group->addChild(geometry);
return group;
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker(int axis)
{
const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f;
const size_t SegmentCount = std::clamp<int>(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64);
const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24;
const size_t VertexCount = SegmentCount * VerticesPerSegment;
const size_t IndexCount = SegmentCount * IndicesPerSegment;
const float Angle = 2 * osg::PI / SegmentCount;
const unsigned short IndexPattern[IndicesPerSegment]
= { 0, 4, 5, 0, 5, 1, 2, 6, 4, 2, 4, 0, 3, 7, 6, 3, 6, 2, 1, 5, 7, 1, 7, 3 };
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(VertexCount);
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1);
osg::ref_ptr<osg::DrawElementsUShort> primitives
= new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth / 4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t index = i * VerticesPerSegment;
float innerX = InnerRadius * std::cos(i * Angle);
float innerY = InnerRadius * std::sin(i * Angle);
float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
}
colors->at(0)
= osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t indices[IndicesPerSegment];
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
indices[j] = i * VerticesPerSegment + j;
if (indices[j] >= VertexCount)
indices[j] -= VertexCount;
}
size_t elementOffset = i * IndicesPerSegment;
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
primitives->setElement(elementOffset++, indices[IndexPattern[j]]);
}
}
geometry->setVertexArray(vertices);
geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group = new osg::Group();
group->addChild(geometry);
return group;
}
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition(float x, float y, float z, int axis)
{
switch (axis)
{
case 2:
return osg::Vec3f(x, y, z);
case 0:
return osg::Vec3f(z, x, y);
case 1:
return osg::Vec3f(y, z, x);
default:
throw std::logic_error("invalid axis for marker geometry");
}
}
CSVRender::Object::Object(
CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero)
: mData(data)
@ -446,8 +189,6 @@ CSVRender::Object::Object(
, mForceBaseToZero(forceBaseToZero)
, mScaleOverride(1)
, mOverrideFlags(0)
, mSubMode(-1)
, mMarkerTransparency(0.5f)
{
mRootNode = new osg::PositionAttitudeTransform;
@ -476,7 +217,6 @@ CSVRender::Object::Object(
adjustTransform();
update();
updateMarker();
}
CSVRender::Object::~Object()
@ -506,9 +246,6 @@ void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSelected() const
@ -536,9 +273,6 @@ void CSVRender::Object::setSnapTarget(bool isSnapTarget)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSnapTarget() const
@ -566,7 +300,6 @@ bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, con
{
adjustTransform();
update();
updateMarker();
return true;
}
@ -614,7 +347,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
= ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData());
update();
updateMarker();
}
return true;
@ -626,7 +358,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
void CSVRender::Object::reloadAssets()
{
update();
updateMarker();
}
std::string CSVRender::Object::getReferenceId() const
@ -720,12 +451,6 @@ void CSVRender::Object::setScale(float scale)
adjustTransform();
}
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
{
const CSMWorld::RefCollection& collection = mData.getReferences();
@ -796,18 +521,8 @@ void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
mOverrideFlags = 0;
}
void CSVRender::Object::setSubMode(int subMode)
{
if (subMode != mSubMode)
{
mSubMode = subMode;
updateMarker();
}
}
void CSVRender::Object::reset()
{
mOverrideFlags = 0;
adjustTransform();
updateMarker();
}

View file

@ -58,14 +58,6 @@ namespace CSVRender
QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override;
};
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class Object
{
public:
@ -76,12 +68,22 @@ namespace CSVRender
Override_Scale = 4
};
private:
static const float MarkerShaftWidth;
static const float MarkerShaftBaseLength;
static const float MarkerHeadWidth;
static const float MarkerHeadLength;
enum SubMode
{
Mode_Move,
Mode_Rotate,
Mode_Scale,
Mode_None,
};
enum Axis
{
Axis_X,
Axis_Y,
Axis_Z
};
private:
CSMWorld::Data& mData;
ESM::RefId mReferenceId;
ESM::RefId mReferenceableId;
@ -96,9 +98,6 @@ namespace CSVRender
ESM::Position mPositionOverride;
float mScaleOverride;
int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
std::unique_ptr<Actor> mActor;
/// Not implemented
@ -120,16 +119,6 @@ namespace CSVRender
/// Throws an exception if *this was constructed with referenceable
const CSMWorld::CellRef& getReference() const;
void updateMarker();
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker(int axis);
osg::ref_ptr<osg::Node> makeRotateMarker(int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition(float x, float y, float z, int axis);
public:
Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable,
bool forceBaseToZero = false);
@ -199,8 +188,6 @@ namespace CSVRender
/// Apply override changes via command and end edit mode
void apply(CSMWorld::CommandMacro& commands);
void setSubMode(int subMode);
/// Erase all overrides and restore the visual representation of the object to its
/// true state.
void reset();

View file

@ -0,0 +1,307 @@
#include <unordered_set>
#include <QFile>
#include <osg/ClipPlane>
#include <osg/Material>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/CullVisitor>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/visitor.hpp>
#include "../../model/prefs/state.hpp"
#include "objectmarker.hpp"
#include "worldspacewidget.hpp"
namespace
{
class FindMaterialVisitor : public osg::NodeVisitor
{
public:
FindMaterialVisitor(CSVRender::NodeMap& map)
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
, mMap(map)
{
}
void apply(osg::Geometry& node) override
{
osg::StateSet* state = node.getStateSet();
if (state->getAttribute(osg::StateAttribute::MATERIAL))
mMap.emplace(node.getName(), &node);
traverse(node);
}
private:
CSVRender::NodeMap& mMap;
};
class ToCamera : public SceneUtil::NodeCallback<ToCamera, osg::Node*, osgUtil::CullVisitor*>
{
public:
ToCamera(osg::ref_ptr<osg::ClipPlane> clipPlane)
: mClipPlane(std::move(clipPlane))
{
}
void operator()(osg::Node* node, osgUtil::CullVisitor* cv)
{
osg::Vec3f normal = cv->getEyePoint();
mClipPlane->setClipPlane(normal.x(), normal.y(), normal.z(), 0);
traverse(node, cv);
}
private:
osg::ref_ptr<osg::ClipPlane> mClipPlane;
};
auto addTagToActiveMarkerNodes = [](CSVRender::NodeMap& mMarkerNodes, CSVRender::Object* object,
std::initializer_list<std::string> suffixes) {
for (const auto& markerSuffix : suffixes)
{
for (char axis = 'X'; axis <= 'Z'; ++axis)
mMarkerNodes[axis + markerSuffix]->setUserData(new CSVRender::ObjectMarkerTag(object, axis - 'X'));
}
};
}
namespace CSVRender
{
ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
ObjectMarker::ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem)
: mWorldspaceWidget(worldspaceWidget)
, mResourceSystem(resourceSystem)
, mMarkerScale(CSMPrefs::get()["Rendering"]["object-marker-scale"].toDouble())
, mSubMode(Object::Mode_None)
{
mBaseNode = new osg::PositionAttitudeTransform;
mBaseNode->setNodeMask(Mask_Reference);
mBaseNode->setScale(osg::Vec3f(mMarkerScale, mMarkerScale, mMarkerScale));
mRootNode = new osg::PositionAttitudeTransform;
mRootNode->addChild(mBaseNode);
worldspaceWidget->setSelectionMarkerRoot(mRootNode);
QFile file(":render/selection-marker");
if (!file.open(QIODevice::ReadOnly))
throw std::runtime_error("Failed to open selection marker file");
auto markerData = file.readAll();
mResourceSystem->getSceneManager()->loadSelectionMarker(mBaseNode, markerData.data(), markerData.size());
osg::ref_ptr<osg::StateSet> baseNodeState = mBaseNode->getOrCreateStateSet();
baseNodeState->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
baseNodeState->setRenderBinDetails(1000, "RenderBin");
FindMaterialVisitor matMapper(mMarkerNodes);
mBaseNode->accept(matMapper);
for (const auto& [name, node] : mMarkerNodes)
{
osg::StateSet* state = node->getStateSet();
osg::Material* mat = static_cast<osg::Material*>(state->getAttribute(osg::StateAttribute::MATERIAL));
osg::Vec4f emis = mat->getEmission(osg::Material::FRONT_AND_BACK);
mat->setEmission(osg::Material::FRONT_AND_BACK, emis / 4);
mOriginalColors.emplace(name, emis);
}
SceneUtil::NodeMap sceneNodes;
SceneUtil::NodeMapVisitor nodeMapper(sceneNodes);
mBaseNode->accept(nodeMapper);
mMarkerNodes.insert(sceneNodes.begin(), sceneNodes.end());
osg::ref_ptr<osg::Node> rotateMarkers = mMarkerNodes["rotateMarkers"];
osg::ClipPlane* clip = new osg::ClipPlane(0);
rotateMarkers->setCullCallback(new ToCamera(clip));
rotateMarkers->getStateSet()->setAttributeAndModes(clip, osg::StateAttribute::ON);
}
void ObjectMarker::toggleVisibility()
{
bool isVisible = mBaseNode->getNodeMask() == Mask_Reference;
mBaseNode->setNodeMask(isVisible ? Mask_Hidden : Mask_Reference);
}
void ObjectMarker::updateScale(const float scale)
{
mMarkerScale = scale;
mBaseNode->setScale(osg::Vec3f(scale, scale, scale));
}
void ObjectMarker::setSubMode(const int subMode)
{
if (subMode == mSubMode)
return;
mSubMode = subMode;
resetMarkerHighlight();
updateSelectionMarker();
}
bool ObjectMarker::hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera)
{
if (mSubMode != Object::Mode_Rotate)
return false;
osg::Vec3d center, eye, forwardVector, _;
std::vector<osg::Node*> rotMark = mMarkerNodes["rotateMarkers"]->getParentalNodePaths()[0];
const osg::Vec3f markerPos = osg::computeLocalToWorld(rotMark).getTrans();
camera->getViewMatrixAsLookAt(eye, center, _);
forwardVector = center - eye;
forwardVector.normalize();
return (hitPos - markerPos) * forwardVector > 0;
}
bool ObjectMarker::attachMarker(const std::string& refId)
{
const auto& object = mWorldspaceWidget->getObjectByReferenceId(refId);
if (!object)
removeFromSelectionHistory(refId);
if (!object || !object->getSelected())
return false;
if (!object->getRootNode()->addChild(mRootNode))
throw std::runtime_error("Failed to add marker to object");
std::string parentMarkerNode;
switch (mSubMode)
{
case (Object::Mode_Rotate):
parentMarkerNode = "rotateMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Rot" });
break;
case (Object::Mode_Scale):
parentMarkerNode = "scaleMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Scale", "_Wall_Scale" });
break;
case (Object::Mode_Move):
default:
parentMarkerNode = "moveMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis", "_Wall" });
break;
}
mMarkerNodes[parentMarkerNode]->asGroup()->setNodeMask(Mask_Reference);
return true;
}
void ObjectMarker::detachMarker()
{
for (std::size_t index = mRootNode->getNumParents(); index > 0;)
mRootNode->getParent(--index)->removeChild(mRootNode);
osg::ref_ptr<osg::Group> widgetRoot = mMarkerNodes["unitArrows"]->asGroup();
for (std::size_t index = widgetRoot->getNumChildren(); index > 0;)
widgetRoot->getChild(--index)->setNodeMask(Mask_Hidden);
}
void ObjectMarker::addToSelectionHistory(const std::string& refId, bool update)
{
auto foundObject = std::find_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; });
if (foundObject == mSelectionHistory.end())
mSelectionHistory.push_back(refId);
else
std::rotate(foundObject, foundObject + 1, mSelectionHistory.end());
if (update)
updateSelectionMarker(refId);
}
void ObjectMarker::removeFromSelectionHistory(const std::string& refId)
{
mSelectionHistory.erase(std::remove_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; }),
mSelectionHistory.end());
}
void ObjectMarker::updateSelectionMarker(const std::string& refId)
{
if (mSelectionHistory.empty())
return;
detachMarker();
if (refId.empty())
{
for (std::size_t index = mSelectionHistory.size(); index > 0;)
if (attachMarker(mSelectionHistory[--index]))
break;
}
else
attachMarker(refId);
}
void ObjectMarker::resetMarkerHighlight()
{
if (mLastHighlightedNodes.empty())
return;
for (const auto& [nodeName, mat] : mLastHighlightedNodes)
mat->setEmission(osg::Material::FRONT_AND_BACK, mat->getEmission(osg::Material::FRONT_AND_BACK) / 4);
mLastHighlightedNodes.clear();
mLastHitNode.clear();
}
void ObjectMarker::updateMarkerHighlight(const std::string_view hitNode, const int axis)
{
if (hitNode == mLastHitNode)
return;
resetMarkerHighlight();
std::string colorName;
switch (axis)
{
case Object::Axis_X:
colorName = "red";
break;
case Object::Axis_Y:
colorName = "green";
break;
case Object::Axis_Z:
colorName = "blue";
break;
default:
throw std::runtime_error("Invalid axis for highlighting: " + std::to_string(axis));
}
std::vector<std::string> targetMaterials = { colorName + "-material" };
if (mSubMode != Object::Mode_Rotate)
targetMaterials.emplace_back(colorName + "_alpha-material");
for (const auto& materialNodeName : targetMaterials)
{
osg::ref_ptr<osg::Node> matNode = mMarkerNodes[materialNodeName];
osg::StateSet* state = matNode->getStateSet();
osg::StateAttribute* matAttr = state->getAttribute(osg::StateAttribute::MATERIAL);
osg::Material* mat = static_cast<osg::Material*>(matAttr);
mat->setEmission(osg::Material::FRONT_AND_BACK, mOriginalColors[materialNodeName]);
mLastHighlightedNodes.emplace(std::make_pair(matNode->getName(), mat));
}
mLastHitNode = hitNode;
}
}

View file

@ -0,0 +1,77 @@
#ifndef OPENCS_VIEW_OBJECT_MARKER_H
#define OPENCS_VIEW_OBJECT_MARKER_H
#include "object.hpp"
namespace osg
{
class Camera;
class Material;
}
namespace CSVRender
{
using NodeMap = std::unordered_map<std::string, osg::ref_ptr<osg::Node>>;
class WorldspaceWidget;
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class ObjectMarker
{
friend class WorldspaceWidget;
WorldspaceWidget* mWorldspaceWidget;
Resource::ResourceSystem* mResourceSystem;
NodeMap mMarkerNodes;
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
osg::ref_ptr<osg::PositionAttitudeTransform> mRootNode;
std::unordered_map<std::string, osg::Vec4f> mOriginalColors;
std::vector<std::string> mSelectionHistory;
std::string mLastHitNode;
std::unordered_map<std::string, osg::Material*> mLastHighlightedNodes;
float mMarkerScale;
int mSubMode;
ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem);
static std::unique_ptr<ObjectMarker> create(WorldspaceWidget* widget, Resource::ResourceSystem* resourceSystem)
{
return std::unique_ptr<ObjectMarker>(new ObjectMarker(widget, resourceSystem));
}
bool attachMarker(const std::string& refId);
void removeFromSelectionHistory(const std::string& refId);
public:
ObjectMarker(ObjectMarker&) = delete;
ObjectMarker(ObjectMarker&&) = delete;
ObjectMarker& operator=(const ObjectMarker&) = delete;
ObjectMarker& operator=(ObjectMarker&&) = delete;
void toggleVisibility();
bool hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera);
void detachMarker();
void addToSelectionHistory(const std::string& refId, bool update = true);
void updateSelectionMarker(const std::string& refId = std::string());
void resetMarkerHighlight();
void updateMarkerHighlight(const std::string_view hitNode, const int axis);
void setSubMode(const int subMode);
void updateScale(const float scale);
};
}
#endif // OPENCS_VIEW_OBJECT_MARKER_H

View file

@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells()
{
modified = true;
auto cell
= std::make_unique<Cell>(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true);
auto cell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode,
iter->first.getId(mWorldspace), deleted, true);
delete iter->second;
iter->second = cell.release();
@ -465,7 +465,8 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene(const CSMWorld::CellCoordi
bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted;
auto cell = std::make_unique<Cell>(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true);
auto cell = std::make_unique<Cell>(
getDocument(), mSelectionMarker.get(), mRootNode, coordinates.getId(mWorldspace), deleted, true);
EditMode* editMode = getEditMode();
cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask());
@ -750,6 +751,7 @@ void CSVRender::PagedWorldspaceWidget::clearSelection(int elementMask)
iter->second->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
void CSVRender::PagedWorldspaceWidget::invertSelection(int elementMask)
@ -907,6 +909,7 @@ void CSVRender::PagedWorldspaceWidget::setSubMode(int subMode, unsigned int elem
{
for (std::map<CSMWorld::CellCoordinates, Cell*>::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter)
iter->second->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask)
@ -986,3 +989,12 @@ void CSVRender::PagedWorldspaceWidget::loadSouthCell()
{
addCellToSceneFromCamera(0, -1);
}
CSVRender::Object* CSVRender::PagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
for (const auto& [_, cell] : mCells)
if (const auto& object = cell->getObjectByReferenceId(referenceId))
return object;
return nullptr;
}

View file

@ -174,6 +174,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId) override;
protected:
void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override;

View file

@ -161,8 +161,6 @@ namespace CSVRender
, mLighting(nullptr)
, mHasDefaultAmbient(false)
, mIsExterior(true)
, mPrevMouseX(0)
, mPrevMouseY(0)
, mCamPositionSet(false)
{
mFreeCamControl = new FreeCameraController(this);
@ -423,10 +421,10 @@ namespace CSVRender
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();
mPrevMouseY = event->y();
mPrevMouse = pos;
}
void SceneWidget::wheelEvent(QWheelEvent* event)
@ -445,6 +443,32 @@ namespace CSVRender
mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp);
mCamPositionSet = true;
}
if (mSelectionMarkerNode)
{
osg::MatrixList worldMats = mSelectionMarkerNode->getWorldMatrices();
if (!worldMats.empty())
{
osg::Matrixd markerWorldMat = worldMats[0];
osg::Vec3f eye, _;
mView->getCamera()->getViewMatrix().getLookAt(eye, _, _);
osg::Vec3f cameraLocalPos = eye * osg::Matrixd::inverse(markerWorldMat);
bool isInFrontRightQuadrant = (cameraLocalPos.x() > 0.1f) && (cameraLocalPos.y() > 0.1f);
bool isSignificantlyBehind = (cameraLocalPos.x() < 1.f) && (cameraLocalPos.y() < 1.f);
if (!isInFrontRightQuadrant && isSignificantlyBehind)
{
osg::Quat current = mSelectionMarkerNode->getAttitude();
mSelectionMarkerNode->setAttitude(current * osg::Quat(osg::PI, osg::Vec3f(0, 0, 1)));
}
float distance = (markerWorldMat.getTrans() - eye).length();
float scale = std::max(distance / 75.0f, 1.0f);
mSelectionMarkerNode->setScale(osg::Vec3(scale, scale, scale));
}
}
}
void SceneWidget::settingChanged(const CSMPrefs::Setting* setting)

View file

@ -9,6 +9,7 @@
#include <QTimer>
#include <QWidget>
#include <osg/PositionAttitudeTransform>
#include <osg/Vec4f>
#include <osg/ref_ptr>
@ -105,6 +106,11 @@ namespace CSVRender
void setExterior(bool isExterior);
void setSelectionMarkerRoot(osg::ref_ptr<osg::PositionAttitudeTransform> selectionMarker)
{
mSelectionMarkerNode = selectionMarker;
}
protected:
void setLighting(Lighting* lighting);
///< \attention The ownership of \a lighting is not transferred to *this.
@ -122,6 +128,7 @@ namespace CSVRender
Lighting* mLighting;
osg::ref_ptr<osg::PositionAttitudeTransform> mSelectionMarkerNode;
osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient;
@ -130,7 +137,7 @@ namespace CSVRender
LightingNight mLightingNight;
LightingBright mLightingBright;
int mPrevMouseX, mPrevMouseY;
QPointF mPrevMouse;
/// Tells update that camera isn't set
bool mCamPositionSet;

View file

@ -1661,7 +1661,7 @@ void CSVRender::TerrainShapeMode::dragMoveEvent(QDragMoveEvent* 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))
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
if (!hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))

View file

@ -724,7 +724,7 @@ void CSVRender::TerrainTextureMode::dragMoveEvent(QDragMoveEvent* 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)
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
if (!hit.hit && mBrushDraw)

View file

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
update();
mCell = std::make_unique<Cell>(document, mRootNode, mCellId);
mCell = std::make_unique<Cell>(document, mSelectionMarker.get(), mRootNode, mCellId);
}
void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId);
mCell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode, mCellId);
mCamPositionSet = false;
mOrbitCamControl->reset();
@ -141,6 +141,7 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection(int elementMask)
{
mCell->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
void CSVRender::UnpagedWorldspaceWidget::invertSelection(int elementMask)
@ -218,6 +219,7 @@ std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget
void CSVRender::UnpagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask)
{
mCell->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask)
@ -383,3 +385,8 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget:
return ignored;
}
}
CSVRender::Object* CSVRender::UnpagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
return mCell->getObjectByReferenceId(referenceId);
}

View file

@ -104,6 +104,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& id) override;
private:
void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override;

View file

@ -52,7 +52,6 @@
#include "cameracontroller.hpp"
#include "instancemode.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "pathgridmode.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent)
@ -74,8 +73,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
, mToolTipPos(-1, -1)
, mShowToolTips(false)
, mToolTipDelay(0)
, mInConstructor(true)
, mSelectedNavigationMode(0)
, mSelectionMarker(ObjectMarker::create(this, document.getData().getResourceSystem().get()))
{
setAcceptDrops(true);
@ -145,13 +144,14 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
&WorldspaceWidget::unhideAll);
connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this] { this->clearSelection(Mask_Reference); });
[this] { clearSelection(Mask_Reference); });
CSMPrefs::Shortcut* switchPerspectiveShortcut = new CSMPrefs::Shortcut("scene-cam-cycle", this);
connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&WorldspaceWidget::cycleNavigationMode);
mInConstructor = false;
connect(new CSMPrefs::Shortcut("scene-toggle-marker", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this]() { mSelectionMarker->toggleVisibility(); });
}
void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* setting)
@ -162,17 +162,8 @@ void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* settin
mDragWheelFactor = setting->toDouble();
else if (*setting == "3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble();
else if (*setting == "Rendering/object-marker-alpha" && !mInConstructor)
{
float alpha = setting->toDouble();
// getSelection is virtual, thus this can not be called from the constructor
auto selection = getSelection(Mask_Reference);
for (osg::ref_ptr<TagBase> tag : selection)
{
if (auto objTag = dynamic_cast<ObjectTag*>(tag.get()))
objTag->mObject->setMarkerTransparency(alpha);
}
}
else if (*setting == "Rendering/object-marker-scale")
mSelectionMarker->updateScale(setting->toDouble());
else if (*setting == "Tooltips/scene-delay")
mToolTipDelay = setting->toInt();
else if (*setting == "Tooltips/scene")
@ -396,8 +387,29 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument()
return mDocument;
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
template <typename Tag>
std::optional<CSVRender::WorldspaceHitResult> CSVRender::WorldspaceWidget::checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const
{
for (auto* node : intersection.nodePath)
{
if (auto* tag = dynamic_cast<Tag*>(node->getUserData()))
{
WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
}
return std::nullopt;
}
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> CSVRender::WorldspaceWidget::getStartEndDirection(
int pointX, int pointY) const
{
// may be okay to just use devicePixelRatio() directly
QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen()
@ -405,8 +417,8 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
: QGuiApplication::primaryScreen();
// (0,0) is considered the lower left corner of an OpenGL window
int x = localPos.x() * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - localPos.y() * screen->devicePixelRatio();
int x = pointX * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - pointY * screen->devicePixelRatio();
// Convert from screen space to world space
osg::Matrixd wpvMat;
@ -418,6 +430,13 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
osg::Vec3d start = wpvMat.preMult(osg::Vec3d(x, y, 0));
osg::Vec3d end = wpvMat.preMult(osg::Vec3d(x, y, 1));
osg::Vec3d direction = end - start;
return { start, end, direction };
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
{
auto [start, end, direction] = getStartEndDirection(localPos.x(), localPos.y());
// Get intersection
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
@ -430,51 +449,46 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
mView->getCamera()->accept(visitor);
// Get relevant data
for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
it != intersector->getIntersections().end(); ++it)
{
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
auto intersections = intersector->getIntersections();
// reject back-facing polygons
if (direction * intersection.getWorldIntersectNormal() > 0)
{
continue;
}
std::vector<osgUtil::LineSegmentIntersector::Intersection> validIntersections
= { intersections.begin(), intersections.end() };
for (std::vector<osg::Node*>::iterator nodeIter = intersection.nodePath.begin();
nodeIter != intersection.nodePath.end(); ++nodeIter)
{
osg::Node* node = *nodeIter;
if (osg::ref_ptr<CSVRender::TagBase> tag = dynamic_cast<CSVRender::TagBase*>(node->getUserData()))
{
WorldspaceHitResult hit = { true, std::move(tag), 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
}
const auto& removeBackfaces = [direction = direction](const osgUtil::LineSegmentIntersector::Intersection& i) {
return direction * i.getWorldIntersectNormal() > 0;
};
// Something untagged, probably terrain
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
validIntersections.erase(std::remove_if(validIntersections.begin(), validIntersections.end(), removeBackfaces),
validIntersections.end());
// Default placement
direction.normalize();
direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt();
WorldspaceHitResult hit = { false, nullptr, 0, 0, 0, start + direction };
if (validIntersections.empty())
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
const auto& firstHit = validIntersections.front();
for (const auto& hit : validIntersections)
if (const auto& markerHit = checkTag<ObjectMarkerTag>(hit))
{
if (mSelectionMarker->hitBehindMarker(markerHit->worldPos, mView->getCamera()))
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
else
return *markerHit;
}
if (auto hit = checkTag<TagBase>(firstHit))
return *hit;
// Something untagged, probably terrain
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, firstHit.getWorldIntersectPoint() };
if (firstHit.indexList.size() >= 3)
{
hit.index0 = firstHit.indexList[0];
hit.index1 = firstHit.indexList[1];
hit.index2 = firstHit.indexList[2];
}
return hit;
}
@ -632,17 +646,53 @@ void CSVRender::WorldspaceWidget::elementSelectionChanged()
void CSVRender::WorldspaceWidget::updateOverlay() {}
void CSVRender::WorldspaceWidget::handleMarkerHighlight(const int x, const int y)
{
auto [start, end, _] = getStartEndDirection(x, y);
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end));
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
osgUtil::IntersectionVisitor visitor(intersector);
visitor.setTraversalMask(Mask_Reference);
mView->getCamera()->accept(visitor);
bool hitMarker = false;
for (const auto& intersection : intersector->getIntersections())
{
if (mSelectionMarker->hitBehindMarker(intersection.getWorldIntersectPoint(), mView->getCamera()))
continue;
for (const auto& node : intersection.nodePath)
{
if (const auto& marker = dynamic_cast<ObjectMarkerTag*>(node->getUserData()))
{
hitMarker = true;
mSelectionMarker->updateMarkerHighlight(node->getName(), marker->mAxis);
break;
}
}
}
if (!hitMarker)
mSelectionMarker->resetMarkerHighlight();
}
void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
{
dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent()).mouseMoveEvent(event);
if (mDragging)
{
int diffX = event->x() - mDragX;
int diffY = (height() - event->y()) - mDragY;
QPoint pos = event->position().toPoint();
int diffX = pos.x() - mDragX;
int diffY = (height() - pos.y()) - mDragY;
mDragX = event->x();
mDragY = height() - event->y();
mDragX = pos.x();
mDragY = height() - pos.y();
double factor = mDragFactor;
@ -651,32 +701,32 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
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)
{
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
if (mDragMode == InteractionType_PrimaryEdit)
mDragging = editMode.primaryEditStartDrag(event->pos());
mDragging = editMode.primaryEditStartDrag(event->position().toPoint());
else if (mDragMode == InteractionType_SecondaryEdit)
mDragging = editMode.secondaryEditStartDrag(event->pos());
mDragging = editMode.secondaryEditStartDrag(event->position().toPoint());
else if (mDragMode == InteractionType_PrimarySelect)
mDragging = editMode.primarySelectStartDrag(event->pos());
mDragging = editMode.primarySelectStartDrag(event->position().toPoint());
else if (mDragMode == InteractionType_SecondarySelect)
mDragging = editMode.secondarySelectStartDrag(event->pos());
mDragging = editMode.secondarySelectStartDrag(event->position().toPoint());
if (mDragging)
{
mDragX = event->localPos().x();
mDragY = height() - event->localPos().y();
mDragX = event->position().x();
mDragY = height() - event->position().y();
}
}
else
{
if (event->globalPos() != mToolTipPos)
if (event->globalPosition().toPoint() != mToolTipPos)
{
mToolTipPos = event->globalPos();
mToolTipPos = event->globalPosition().toPoint();
if (mShowToolTips)
{
@ -685,6 +735,8 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
}
}
QPoint pos = event->position().toPoint();
handleMarkerHighlight(pos.x(), pos.y());
SceneWidget::mouseMoveEvent(event);
}
}

View file

@ -13,6 +13,7 @@
#include <apps/opencs/view/render/tagbase.hpp>
#include "instancedragmodes.hpp"
#include "objectmarker.hpp"
#include "scenewidget.hpp"
class QDragEnterEvent;
@ -89,7 +90,6 @@ namespace CSVRender
QPoint mToolTipPos;
bool mShowToolTips;
int mToolTipDelay;
bool mInConstructor;
int mSelectedNavigationMode;
public:
@ -186,6 +186,12 @@ namespace CSVRender
virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0;
template <typename Tag>
std::optional<WorldspaceHitResult> checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const;
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> getStartEndDirection(int pointX, int pointY) const;
/// Return the next intersection with scene elements matched by
/// \a interactionMask based on \a localPos and the camera vector.
/// If there is no such intersection, instead a point "in front" of \a localPos will be
@ -216,7 +222,14 @@ namespace CSVRender
EditMode* getEditMode();
virtual CSVRender::Object* getObjectByReferenceId(const std::string& id) = 0;
ObjectMarker* getSelectionMarker() { return mSelectionMarker.get(); }
const ObjectMarker* getSelectionMarker() const { return mSelectionMarker.get(); }
protected:
const std::unique_ptr<CSVRender::ObjectMarker> mSelectionMarker;
/// Visual elements in a scene
/// @note do not change the enumeration values, they are used in pre-existing button file names!
enum ButtonId
@ -247,11 +260,13 @@ namespace CSVRender
void settingChanged(const CSMPrefs::Setting* setting) override;
bool getSpeedMode();
void cycleNavigationMode();
private:
bool hitBehindMarker(const osg::Vec3d& hitPos) const;
void handleMarkerHighlight(const int x, const int y);
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;

View file

@ -93,7 +93,7 @@ void CSVTools::ReportTable::contextMenuEvent(QContextMenuEvent* event)
void CSVTools::ReportTable::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton)
startDragFromTable(*this, indexAt(event->pos()));
startDragFromTable(*this, indexAt(event->position().toPoint()));
}
void CSVTools::ReportTable::mouseDoubleClickEvent(QMouseEvent* event)

View file

@ -52,7 +52,7 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent* event)
// 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
if (buttonRect.contains(event->globalPos()) || buttonRect.contains(event->pos()))
if (buttonRect.contains(event->globalPosition().toPoint()) || buttonRect.contains(event->position().toPoint()))
{
setAttribute(Qt::WA_NoMouseReplay);
}

View file

@ -22,12 +22,8 @@ int CSVWidget::CompleterPopup::sizeHintForRow(int row) const
ensurePolished();
QModelIndex index = model()->index(row, modelColumn());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QStyleOptionViewItem option;
initViewItemOption(&option);
#else
QStyleOptionViewItem option = viewOptions();
#endif
QAbstractItemDelegate* delegate = itemDelegate(index);
QAbstractItemDelegate* delegate = itemDelegateForIndex(index);
return delegate->sizeHint(option, index).height();
}

View file

@ -85,7 +85,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData(QWidget* editor, const QMod
CSMWorld::Columns::ColumnId columnId
= static_cast<CSMWorld::Columns::ColumnId>(mTable->getColumnId(index.column()));
if (QVariant::String == v.type())
if (QMetaType::QString == v.typeId())
{
label->setText(v.toString());
}

View file

@ -55,7 +55,7 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent* 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))
|| CSVWorld::DragDropUtils::isInfo(*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)
{
QModelIndex index = indexAt(event->pos());
QModelIndex index = indexAt(event->position().toPoint());
CSMWorld::ColumnBase::Display display = getIndexDisplayType(index);
if (CSVWorld::DragDropUtils::canAcceptData(*event, display))
{

View file

@ -341,7 +341,7 @@ void CSVWorld::RegionMap::viewInTable()
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
@ -376,7 +376,7 @@ void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* 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);
if (!index.isValid() || !exists)

View file

@ -21,6 +21,24 @@
#include "../../model/world/tablemimedata.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)
: mEdit(edit)
{
@ -46,6 +64,55 @@ bool CSVWorld::ScriptEdit::event(QEvent* 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)
: QPlainTextEdit(parent)
, mChangeLocked(0)
@ -136,7 +203,7 @@ void CSVWorld::ScriptEdit::dragEnterEvent(QDragEnterEvent* event)
QPlainTextEdit::dragEnterEvent(event);
else
{
setTextCursor(cursorForPosition(event->pos()));
setTextCursor(cursorForPosition(event->position().toPoint()));
event->acceptProposedAction();
}
}
@ -148,7 +215,7 @@ void CSVWorld::ScriptEdit::dragMoveEvent(QDragMoveEvent* event)
QPlainTextEdit::dragMoveEvent(event);
else
{
setTextCursor(cursorForPosition(event->pos()));
setTextCursor(cursorForPosition(event->position().toPoint()));
event->accept();
}
}
@ -162,7 +229,7 @@ void CSVWorld::ScriptEdit::dropEvent(QDropEvent* event)
return;
}
setTextCursor(cursorForPosition(event->pos()));
setTextCursor(cursorForPosition(event->position().toPoint()));
if (mime->fromDocument(mDocument))
{
@ -316,22 +383,7 @@ void CSVWorld::ScriptEdit::markOccurrences()
void CSVWorld::ScriptEdit::commentSelection()
{
QTextCursor begin = 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();
prependToEachLine(textCursor(), ";");
}
void CSVWorld::ScriptEdit::uncommentSelection()
@ -345,17 +397,16 @@ void CSVWorld::ScriptEdit::uncommentSelection()
end.movePosition(QTextCursor::EndOfLine);
begin.beginEditBlock();
for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right))
{
begin.select(QTextCursor::LineUnderCursor);
QString line = begin.selectedText();
if (line.size() == 0)
if (line.isEmpty())
continue;
// get first nonspace character in line
int index;
qsizetype index;
for (index = 0; index != line.size(); ++index)
{
if (!line[index].isSpace())

View file

@ -74,6 +74,7 @@ namespace CSVWorld
protected:
bool event(QEvent* event) override;
void keyPressEvent(QKeyEvent* e) override;
public:
ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent);

View file

@ -592,7 +592,7 @@ void CSVWorld::Table::moveRecords(QDropEvent* event)
if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
return;
QModelIndex targedIndex = indexAt(event->pos());
QModelIndex targedIndex = indexAt(event->position().toPoint());
QModelIndexList selectedRows = selectionModel()->selectedRows();
int targetRowRaw = targedIndex.row();
@ -872,7 +872,7 @@ void CSVWorld::Table::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton)
{
startDragFromTable(*this, indexAt(event->pos()));
startDragFromTable(*this, indexAt(event->position().toPoint()));
}
}

View file

@ -31,7 +31,7 @@ namespace CSVWorld
auto& clickEvent = static_cast<QMouseEvent&>(*event);
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);
clickEvent.accept();
return true;

View file

@ -171,7 +171,7 @@ QWidget* CSVWorld::CommandDelegate::createEditor(
// TODO: Find a better solution?
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
// (the third parameter of ColorEditor's constructor)
@ -362,11 +362,7 @@ void CSVWorld::CommandDelegate::setEditorData(QWidget* editor, const QModelIndex
if (!n.isEmpty())
{
if (!variant.isValid())
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
variant = QVariant(editor->property(n).metaType(), (const void*)nullptr);
#else
variant = QVariant(editor->property(n).userType(), (const void*)nullptr);
#endif
editor->setProperty(n, variant);
}
}

View file

@ -44,7 +44,7 @@ add_openmw_dir (mwgui
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
postprocessorhud settings
postprocessorhud settings worlditemmodel itemtransfer
)
add_openmw_dir (mwdialogue
@ -63,7 +63,7 @@ add_openmw_dir (mwlua
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings coremwscriptbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings
postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings weatherbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
types/potion types/ingredient types/misc types/repair types/armor types/light types/static

View file

@ -433,6 +433,8 @@ OMW::Engine::~Engine()
}
SDL_Quit();
Log(Debug::Info) << "Quitting peacefully.";
}
// Set data dir
@ -729,7 +731,7 @@ void OMW::Engine::prepareEngine()
mVFS = std::make_unique<VFS::Manager>();
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true, &mEncoder.get()->getStatelessEncoder());
mResourceSystem = std::make_unique<Resource::ResourceSystem>(
mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder());
@ -753,7 +755,7 @@ void OMW::Engine::prepareEngine()
mViewer->addEventHandler(mScreenCaptureHandler);
mL10nManager = std::make_unique<l10n::Manager>(mVFS.get());
mL10nManager = std::make_unique<L10n::Manager>(mVFS.get());
mL10nManager->setPreferredLocales(Settings::general().mPreferredLocales, Settings::general().mGmstOverridesL10n);
mEnvironment.setL10nManager(*mL10nManager);
@ -779,7 +781,6 @@ void OMW::Engine::prepareEngine()
const auto userdefault = mCfgMgr.getUserConfigPath() / "gamecontrollerdb.txt";
const auto localdefault = mCfgMgr.getLocalPath() / "gamecontrollerdb.txt";
const auto globaldefault = mCfgMgr.getGlobalPath() / "gamecontrollerdb.txt";
std::filesystem::path userGameControllerdb;
if (std::filesystem::exists(userdefault))
@ -788,9 +789,13 @@ void OMW::Engine::prepareEngine()
std::filesystem::path gameControllerdb;
if (std::filesystem::exists(localdefault))
gameControllerdb = localdefault;
else if (std::filesystem::exists(globaldefault))
gameControllerdb = globaldefault;
// else if it doesn't exist, pass in an empty string
else if (!mCfgMgr.getGlobalPath().empty())
{
const auto globaldefault = mCfgMgr.getGlobalPath() / "gamecontrollerdb.txt";
if (std::filesystem::exists(globaldefault))
gameControllerdb = globaldefault;
}
// else if it doesn't exist, pass in an empty path
// gui needs our shaders path before everything else
mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders");
@ -1069,8 +1074,6 @@ void OMW::Engine::go()
Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg");
Settings::ShaderManager::get().save();
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath());
Log(Debug::Info) << "Quitting peacefully.";
}
void OMW::Engine::setCompileAll(bool all)

View file

@ -113,7 +113,7 @@ namespace MWDialogue
class Journal;
}
namespace l10n
namespace L10n
{
class Manager;
}
@ -141,7 +141,7 @@ namespace OMW
std::unique_ptr<MWState::StateManager> mStateManager;
std::unique_ptr<MWLua::LuaManager> mLuaManager;
std::unique_ptr<MWLua::Worker> mLuaWorker;
std::unique_ptr<l10n::Manager> mL10nManager;
std::unique_ptr<L10n::Manager> mL10nManager;
MWBase::Environment mEnvironment;
ToUTF8::FromType mEncoding;
std::unique_ptr<ToUTF8::Utf8Encoder> mEncoder;

View file

@ -10,7 +10,7 @@ namespace Resource
class ResourceSystem;
}
namespace l10n
namespace L10n
{
class Manager;
}
@ -57,7 +57,7 @@ namespace MWBase
StateManager* mStateManager = nullptr;
LuaManager* mLuaManager = nullptr;
Resource::ResourceSystem* mResourceSystem = nullptr;
l10n::Manager* mL10nManager = nullptr;
L10n::Manager* mL10nManager = nullptr;
float mFrameRateLimit = 0;
float mFrameDuration = 0;
@ -95,7 +95,7 @@ namespace MWBase
void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; }
void setL10nManager(l10n::Manager& value) { mL10nManager = &value; }
void setL10nManager(L10n::Manager& value) { mL10nManager = &value; }
Misc::NotNullPtr<World> getWorld() const { return mWorld; }
Misc::NotNullPtr<MWWorld::WorldModel> getWorldModel() const { return mWorldModel; }
@ -122,7 +122,7 @@ namespace MWBase
Misc::NotNullPtr<Resource::ResourceSystem> getResourceSystem() const { return mResourceSystem; }
Misc::NotNullPtr<l10n::Manager> getL10nManager() const { return mL10nManager; }
Misc::NotNullPtr<L10n::Manager> getL10nManager() const { return mL10nManager; }
float getFrameRateLimit() const { return mFrameRateLimit; }

View file

@ -88,6 +88,8 @@ namespace MWBase
virtual void executeAction(int action) = 0;
virtual bool controlsDisabled() = 0;
virtual void saveBindings() = 0;
};
}

View file

@ -1,6 +1,7 @@
#ifndef GAME_MWBASE_LUAMANAGER_H
#define GAME_MWBASE_LUAMANAGER_H
#include <filesystem>
#include <map>
#include <string>
#include <variant>
@ -8,6 +9,7 @@
#include <SDL_events.h>
#include "../mwgui/mode.hpp"
#include "../mwmechanics/damagesourcetype.hpp"
#include "../mwrender/animationpriority.hpp"
#include <components/sdlutil/events.hpp>
@ -38,6 +40,11 @@ namespace LuaUtil
}
}
namespace osg
{
class Vec3f;
}
namespace MWBase
{
// \brief LuaManager is the central interface through which the engine invokes lua scripts.
@ -68,13 +75,19 @@ namespace MWBase
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)
= 0;
virtual void jailTimeServed(const MWWorld::Ptr& actor, int days) = 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 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 actorDied(const MWWorld::Ptr& actor) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// `arg` is either forwarded from MWGui::pushGuiMode or empty
virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0;
virtual void savePermanentStorage(const std::filesystem::path& userConfigPath) = 0;
// TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,

View file

@ -8,9 +8,6 @@
#include <string_view>
#include <vector>
#include "../mwmechanics/greetingstate.hpp"
#include "../mwrender/animationpriority.hpp"
#include "../mwworld/ptr.hpp"
namespace osg
@ -27,6 +24,11 @@ namespace ESM
class ESMWriter;
}
namespace MWMechanics
{
enum class GreetingState;
}
namespace MWWorld
{
class Ptr;

View file

@ -384,6 +384,7 @@ namespace MWBase
// Used in Lua bindings
virtual const std::vector<MWGui::GuiMode>& getGuiModeStack() const = 0;
virtual void setDisabledByLua(std::string_view windowId, bool disabled) = 0;
virtual bool isWindowVisible(std::string_view windowId) const = 0;
virtual std::vector<std::string_view> getAllWindowIds() const = 0;
virtual std::vector<std::string_view> getAllowedWindowIds(MWGui::GuiMode mode) const = 0;
};

View file

@ -22,6 +22,7 @@
namespace osg
{
class Vec3f;
class Vec4f;
class Matrixf;
class Quat;
class Image;
@ -93,6 +94,7 @@ namespace MWWorld
class RefData;
class Cell;
class DateTimeManager;
class Weather;
typedef std::vector<std::pair<MWWorld::Ptr, MWMechanics::Movement>> PtrMovementList;
}
@ -216,9 +218,21 @@ namespace MWBase
virtual void changeWeather(const ESM::RefId& region, const unsigned int id) = 0;
virtual int getCurrentWeather() const = 0;
virtual void changeWeather(const ESM::RefId& region, const ESM::RefId& id) = 0;
virtual int getNextWeather() const = 0;
virtual const std::vector<MWWorld::Weather>& getAllWeather() const = 0;
virtual int getCurrentWeatherScriptId() const = 0;
virtual const MWWorld::Weather& getCurrentWeather() const = 0;
virtual const MWWorld::Weather* getWeather(size_t index) const = 0;
virtual const MWWorld::Weather* getWeather(const ESM::RefId& id) const = 0;
virtual int getNextWeatherScriptId() const = 0;
virtual const MWWorld::Weather* getNextWeather() const = 0;
virtual float getWeatherTransition() const = 0;
@ -478,6 +492,7 @@ namespace MWBase
// Allow NPCs to use torches?
virtual bool useTorches() const = 0;
virtual const osg::Vec4f& getSunLightPosition() const = 0;
virtual float getSunVisibility() const = 0;
virtual float getSunPercentage() const = 0;
@ -511,9 +526,6 @@ namespace MWBase
/// Spawn a random creature from a levelled list next to the player
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,
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true)
= 0;
@ -574,8 +586,7 @@ namespace MWBase
virtual bool hasCollisionWithDoor(
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
virtual bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const = 0;
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;

View file

@ -12,6 +12,8 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwlua/localscripts.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/containerstore.hpp"
@ -109,8 +111,23 @@ namespace MWClass
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>();
std::string_view typeGmst;
@ -175,7 +192,7 @@ namespace MWClass
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 mediumUp = ESM::RefId::stringRefId("Item Armor Medium 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 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 mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down");
static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down");
@ -221,24 +238,29 @@ namespace MWClass
std::string text;
// get armor type string (light/medium/heavy)
std::string_view typeText;
std::string typeText;
if (ref->mBase->mData.mWeight == 0)
{
// no type
}
else
{
const ESM::RefId armorType = getEquipmentSkill(ptr);
const ESM::RefId armorType = getEquipmentSkill(ptr, true);
if (armorType == ESM::Skill::LightArmor)
typeText = "#{sLight}";
else if (armorType == ESM::Skill::MediumArmor)
typeText = "#{sMedium}";
else
else if (armorType == ESM::Skill::HeavyArmor)
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}: "
+ 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);
text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/"
@ -289,11 +311,25 @@ namespace MWClass
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 ESM::RefId armorSkillType = getEquipmentSkill(ptr);
const ESM::RefId armorSkillType = getEquipmentSkill(ptr, useLuaInterfaceIfAvailable);
float armorSkill = actor.getClass().getSkill(actor, armorSkillType);
int iBaseArmorSkill = MWBase::Environment::get()

View file

@ -41,7 +41,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// 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;
///< @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;
/// 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;
};
}

View file

@ -98,7 +98,7 @@ namespace MWClass
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>();

View file

@ -33,7 +33,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// 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;
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.

View file

@ -192,12 +192,13 @@ namespace MWClass
{
if (!isTrapped)
{
if (canBeHarvested(ptr))
{
return std::make_unique<MWWorld::ActionHarvest>(ptr);
}
if (!canBeHarvested(ptr))
return std::make_unique<MWWorld::ActionOpen>(ptr);
return std::make_unique<MWWorld::ActionOpen>(ptr);
if (hasToolTip(ptr))
return std::make_unique<MWWorld::ActionHarvest>(ptr);
return std::make_unique<MWWorld::FailedAction>(std::string_view{}, ptr);
}
else
{

View file

@ -25,11 +25,14 @@
#include "../mwmechanics/setbaseaisetting.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwlua/localscripts.hpp"
#include "../mwworld/actionopen.hpp"
#include "../mwworld/actiontalk.hpp"
#include "../mwworld/cellstore.hpp"
@ -283,8 +286,8 @@ namespace MWClass
if (!success)
{
victim.getClass().onHit(
victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
0.0f, false, hitPosition, false, MWMechanics::DamageSourceType::Melee);
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
return;
}
@ -342,12 +345,12 @@ namespace MWClass
MWMechanics::diseaseContact(victim, ptr);
victim.getClass().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 Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
void Creature::onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages,
const MWWorld::Ptr& object, const MWWorld::Ptr& attacker, bool successful,
const MWMechanics::DamageSourceType sourceType) const
{
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
@ -360,16 +363,12 @@ namespace MWClass
{
stats.setAttacked(true);
// No retaliation for totally static creatures (they have no movement or attacks anyway)
if (isMobile(ptr))
{
bool complain = sourceType == MWMechanics::DamageSourceType::Melee;
bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged;
if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain))
setOnPcHitMe = false;
else
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
}
bool complain = sourceType == MWMechanics::DamageSourceType::Melee;
bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged;
if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain))
setOnPcHitMe = false;
else
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
}
// Attacker and target store each other as hitattemptactor if they have no one stored yet
@ -401,19 +400,44 @@ namespace MWClass
if (!successful)
{
// Missed
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
MWBase::Environment::get().getSoundManager()->playSound3D(
ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
return;
}
if (!object.isEmpty())
stats.setLastHitObject(object.getCellRef().getRefId());
if (damage < 0.001f)
damage = 0;
bool hasDamage = false;
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())
{
@ -424,35 +448,11 @@ namespace MWClass
* getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f
+ getGmst().iKnockDownOddsBase->mValue.getInteger();
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);
else
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);
}
}
}
@ -534,10 +534,11 @@ namespace MWClass
const MWBase::World* world = MWBase::Environment::get().getWorld();
const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects();
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
float moveSpeed;
if (getEncumbrance(ptr) > getCapacity(ptr))
if (normalizedEncumbrance > 1.0f)
moveSpeed = 0.0f;
else if (canFly(ptr)
|| (mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled()))
@ -547,7 +548,6 @@ namespace MWClass
+ mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude());
flySpeed = gmst.fMinFlySpeed->mValue.getFloat()
+ flySpeed * (gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat());
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance;
flySpeed = std::max(0.0f, flySpeed);
moveSpeed = flySpeed;
@ -595,7 +595,7 @@ namespace MWClass
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.
return getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude();
@ -768,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
{
if (!state.mHasCustomState)
@ -871,7 +866,8 @@ namespace MWClass
ptr.getRefData().setCustomData(nullptr);
// Reset to original position
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
MWBase::Environment::get().getWorld()->moveObject(
ptr, ptr.getCell()->getOriginCell(ptr), ptr.getCellRef().getPosition().asVec3());
MWBase::Environment::get().getWorld()->rotateObject(
ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none);
}

View file

@ -66,8 +66,8 @@ namespace MWClass
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
const osg::Vec3f& hitPosition, bool success) const override;
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
void onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, bool successful,
const MWMechanics::DamageSourceType sourceType) 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
/// 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
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;
/// 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;
///< Read additional state from \a state into \a ptr.

View file

@ -55,6 +55,16 @@ namespace MWClass
}
return res;
}
// TODO: Figure out a better way to find markers and LOD meshes
inline bool isMarkerModel(std::string_view model)
{
return Misc::StringUtils::ciStartsWith(model, "marker");
}
inline bool isLodModel(std::string_view model)
{
return Misc::StringUtils::ciEndsWith(model, "lod.nif");
}
}
// Base for many ESM4 Classes
@ -100,11 +110,8 @@ namespace MWClass
{
std::string_view model = getClassModel<Record>(ptr);
// Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack.
// Needed because otherwise LOD meshes are rendered on top of normal meshes.
// TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid.
if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker")
|| Misc::StringUtils::ciEndsWith(model, "lod.nif"))
// TODO: There should be a better way to hide markers
if (ESM4Impl::isMarkerModel(model) || ESM4Impl::isLodModel(model))
return {};
return model;

View file

@ -29,6 +29,8 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwlua/localscripts.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/aisetting.hpp"
#include "../mwmechanics/autocalcspell.hpp"
@ -620,8 +622,8 @@ namespace MWClass
float damage = 0.0f;
if (!success)
{
othercls.onHit(
victim, damage, false, weapon, ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
MWBase::Environment::get().getLuaManager()->onHit(ptr, victim, weapon, MWWorld::Ptr(), type, attackStrength,
damage, false, hitPosition, false, MWMechanics::DamageSourceType::Melee);
MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr);
MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
return;
@ -694,14 +696,13 @@ namespace MWClass
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,
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
const MWMechanics::DamageSourceType sourceType) const
void Npc::onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, bool successful, const MWMechanics::DamageSourceType sourceType) const
{
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
bool wasDead = stats.isDead();
@ -748,23 +749,47 @@ namespace MWClass
if (!successful)
{
// Missed
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
return;
}
if (!object.isEmpty())
stats.setLastHitObject(object.getCellRef().getRefId());
if (damage < 0.001f)
damage = 0;
if (ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
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)
damage = 0;
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 (damage > 0.0f && !attacker.isEmpty())
if (hasDamage && !attacker.isEmpty())
{
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
// something, alert the character controller, scripts, etc.
@ -783,109 +808,16 @@ namespace MWClass
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
* gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f
+ 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);
else
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)
damage = scaleDamage(damage, attacker, ptr);
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 (ptr == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
}
if (!wasDead && getCreatureStats(ptr).isDead())
@ -990,15 +922,10 @@ namespace MWClass
const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects();
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
bool swimming = world->isSwimming(ptr);
bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr);
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr);
running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr));
const bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr);
float moveSpeed;
if (getEncumbrance(ptr) > getCapacity(ptr))
if (normalizedEncumbrance > 1.0f)
moveSpeed = 0.0f;
else if (mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled())
{
@ -1011,9 +938,9 @@ namespace MWClass
flySpeed = std::max(0.0f, flySpeed);
moveSpeed = flySpeed;
}
else if (swimming)
else if (world->isSwimming(ptr))
moveSpeed = getSwimSpeed(ptr);
else if (running && !sneaking)
else if (running && !MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr))
moveSpeed = getRunSpeed(ptr);
else
moveSpeed = getWalkSpeed(ptr);
@ -1141,8 +1068,17 @@ namespace MWClass
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
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -1164,7 +1100,7 @@ namespace MWClass
}
else
{
ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr);
ratings[i] = it->getClass().getSkillAdjustedArmorRating(*it, ptr);
// Take in account armor condition
const bool hasHealth = it->getClass().hasItemHealth(*it);
@ -1313,11 +1249,6 @@ namespace MWClass
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
{
if (!state.mHasCustomState)
@ -1427,7 +1358,8 @@ namespace MWClass
ptr.getRefData().setCustomData(nullptr);
// Reset to original position
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
MWBase::Environment::get().getWorld()->moveObject(
ptr, ptr.getCell()->getOriginCell(ptr), ptr.getCellRef().getPosition().asVec3());
MWBase::Environment::get().getWorld()->rotateObject(
ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none);
}
@ -1508,14 +1440,8 @@ namespace MWClass
float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const
{
const MWBase::World* world = MWBase::Environment::get().getWorld();
const MWMechanics::NpcStats& stats = getNpcStats(ptr);
const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects();
const bool swimming = world->isSwimming(ptr);
const bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr);
const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run)
&& (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr));
return getSwimSpeedImpl(ptr, getGmst(), mageffects, running ? getRunSpeed(ptr) : getWalkSpeed(ptr));
const MWMechanics::MagicEffects& effects = getNpcStats(ptr).getMagicEffects();
const bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr);
return getSwimSpeedImpl(ptr, getGmst(), effects, running ? getRunSpeed(ptr) : getWalkSpeed(ptr));
}
}

View file

@ -81,8 +81,8 @@ namespace MWClass
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
const osg::Vec3f& hitPosition, bool success) const override;
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
void onHit(const MWWorld::Ptr& ptr, const std::map<std::string, float>& damages, const MWWorld::Ptr& object,
const MWWorld::Ptr& attacker, bool successful,
const MWMechanics::DamageSourceType sourceType) 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
/// 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
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;
/// 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; }
void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override;

View file

@ -105,7 +105,7 @@ namespace MWClass
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>();
int type = ref->mBase->mData.mType;
@ -270,14 +270,14 @@ namespace MWClass
std::pair<int, std::string_view> Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const
{
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return { 0, "#{sInventoryMessage1}" };
// Do not allow equip weapons from inventory during attack
if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode()
&& MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc))
return { 0, "#{sCantEquipWeapWarning}" };
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return { 0, "#{sInventoryMessage1}" };
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
if (slots_.first.empty())

View file

@ -42,7 +42,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// 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;
///< Return trade value of the object. Throws an exception, if the object can't be traded.

View file

@ -12,6 +12,7 @@
#include <components/esm3/loaddial.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/compiler/errorhandler.hpp>
#include <components/compiler/exception.hpp>
@ -448,12 +449,14 @@ namespace MWDialogue
{
updateOriginalDisposition();
MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor);
// Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with
// intimidate)
npcStats.setBaseDisposition(0);
int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false);
int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero);
// Get the sum of disposition effects minus charm (shouldn't be made permanent)
npcStats.setBaseDisposition(0);
int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)
- npcStats.getMagicEffects().getOrDefault(ESM::MagicEffect::Charm).getMagnitude();
// Clamp new permanent disposition to avoid negative derived disposition (can be caused by intimidate)
int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero);
npcStats.setBaseDisposition(disposition);
}
mPermanentDispositionChange = 0;

View file

@ -501,7 +501,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
case ESM::DialogueCondition::Function_Weather:
return MWBase::Environment::get().getWorld()->getCurrentWeather();
return MWBase::Environment::get().getWorld()->getCurrentWeatherScriptId();
case ESM::DialogueCondition::Function_Reputation:
if (!mActor.getClass().isNpc())

View file

@ -14,6 +14,7 @@
#include "companionitemmodel.hpp"
#include "countdialog.hpp"
#include "draganddrop.hpp"
#include "itemtransfer.hpp"
#include "itemview.hpp"
#include "messagebox.hpp"
#include "sortfilteritemmodel.hpp"
@ -38,12 +39,14 @@ namespace
namespace MWGui
{
CompanionWindow::CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager)
CompanionWindow::CompanionWindow(DragAndDrop& dragAndDrop, ItemTransfer& itemTransfer, MessageBoxManager* manager)
: WindowBase("openmw_companion_window.layout")
, mSortModel(nullptr)
, mModel(nullptr)
, mSelectedItem(-1)
, mDragAndDrop(dragAndDrop)
, mUpdateNextFrame(false)
, mDragAndDrop(&dragAndDrop)
, mItemTransfer(&itemTransfer)
, mMessageBoxManager(manager)
{
getWidget(mCloseButton, "CloseButton");
@ -93,8 +96,14 @@ namespace MWGui
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, "#{sTake}", count);
dialog->eventOkClicked.clear();
dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem);
if (MyGUI::InputManager::getInstance().isAltPressed())
dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem);
}
else if (MyGUI::InputManager::getInstance().isAltPressed())
transferItem(nullptr, count);
else
dragItem(nullptr, count);
}
@ -105,11 +114,16 @@ namespace MWGui
mItemView->update();
}
void CompanionWindow::dragItem(MyGUI::Widget* sender, int count)
void CompanionWindow::dragItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count);
}
void CompanionWindow::transferItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
mItemTransfer->apply(mModel->getItem(mSelectedItem), count, *mItemView);
}
void CompanionWindow::onBackgroundSelected()
{
if (mDragAndDrop->mIsOnDragAndDrop)
@ -134,12 +148,20 @@ namespace MWGui
mItemView->resetScrollBars();
setTitle(actor.getClass().getName(actor));
mPtr.getClass().getContainerStore(mPtr).setContListener(this);
}
void CompanionWindow::onFrame(float dt)
{
checkReferenceAvailable();
updateEncumbranceBar();
if (mUpdateNextFrame)
{
updateEncumbranceBar();
mItemView->update();
mUpdateNextFrame = false;
}
}
void CompanionWindow::updateEncumbranceBar()
@ -202,4 +224,23 @@ namespace MWGui
mSortModel = nullptr;
}
void CompanionWindow::itemAdded(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
void CompanionWindow::itemRemoved(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
void CompanionWindow::onOpen()
{
mItemTransfer->addTarget(*mItemView);
}
void CompanionWindow::onClose()
{
mItemTransfer->removeTarget(*mItemView);
}
}

View file

@ -4,6 +4,10 @@
#include "referenceinterface.hpp"
#include "windowbase.hpp"
#include "../mwworld/containerstore.hpp"
#include <components/misc/notnullptr.hpp>
namespace MWGui
{
namespace Widgets
@ -16,11 +20,12 @@ namespace MWGui
class DragAndDrop;
class SortFilterItemModel;
class CompanionItemModel;
class ItemTransfer;
class CompanionWindow : public WindowBase, public ReferenceInterface
class CompanionWindow : public WindowBase, public ReferenceInterface, public MWWorld::ContainerStoreListener
{
public:
CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager);
explicit CompanionWindow(DragAndDrop& dragAndDrop, ItemTransfer& itemTransfer, MessageBoxManager* manager);
bool exit() override;
@ -30,6 +35,9 @@ namespace MWGui
void onFrame(float dt) override;
void clear() override { resetReference(); }
void itemAdded(const MWWorld::ConstPtr& item, int count) override;
void itemRemoved(const MWWorld::ConstPtr& item, int count) override;
std::string_view getWindowIdForLua() const override { return "Companion"; }
private:
@ -37,8 +45,10 @@ namespace MWGui
SortFilterItemModel* mSortModel;
CompanionItemModel* mModel;
int mSelectedItem;
bool mUpdateNextFrame;
DragAndDrop* mDragAndDrop;
Misc::NotNullPtr<DragAndDrop> mDragAndDrop;
Misc::NotNullPtr<ItemTransfer> mItemTransfer;
MyGUI::Button* mCloseButton;
MyGUI::EditBox* mFilterEdit;
@ -49,7 +59,8 @@ namespace MWGui
void onItemSelected(int index);
void onNameFilterChanged(MyGUI::EditBox* _sender);
void onBackgroundSelected();
void dragItem(MyGUI::Widget* sender, int count);
void dragItem(MyGUI::Widget* sender, std::size_t count);
void transferItem(MyGUI::Widget* sender, std::size_t count);
void onMessageBoxButtonClicked(int button);
@ -58,6 +69,10 @@ namespace MWGui
void onCloseButtonClicked(MyGUI::Widget* _sender);
void onReferenceUnavailable() override;
void onOpen() override;
void onClose() override;
};
}

View file

@ -18,12 +18,12 @@
#include "../mwscript/interpretercontext.hpp"
#include "countdialog.hpp"
#include "inventorywindow.hpp"
#include "containeritemmodel.hpp"
#include "countdialog.hpp"
#include "draganddrop.hpp"
#include "inventoryitemmodel.hpp"
#include "inventorywindow.hpp"
#include "itemtransfer.hpp"
#include "itemview.hpp"
#include "pickpocketitemmodel.hpp"
#include "sortfilteritemmodel.hpp"
@ -32,12 +32,14 @@
namespace MWGui
{
ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop)
ContainerWindow::ContainerWindow(DragAndDrop& dragAndDrop, ItemTransfer& itemTransfer)
: WindowBase("openmw_container_window.layout")
, mDragAndDrop(dragAndDrop)
, mDragAndDrop(&dragAndDrop)
, mItemTransfer(&itemTransfer)
, mSortModel(nullptr)
, mModel(nullptr)
, mSelectedItem(-1)
, mUpdateNextFrame(false)
, mTreatNextOpenAsLoot(false)
{
getWidget(mDisposeCorpseButton, "DisposeCorpseButton");
@ -88,26 +90,47 @@ namespace MWGui
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, "#{sTake}", count);
dialog->eventOkClicked.clear();
dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem);
if (MyGUI::InputManager::getInstance().isAltPressed())
dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem);
}
else if (MyGUI::InputManager::getInstance().isAltPressed())
transferItem(nullptr, count);
else
dragItem(nullptr, count);
}
void ContainerWindow::dragItem(MyGUI::Widget* sender, int count)
void ContainerWindow::dragItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
if (!mModel)
if (mModel == nullptr)
return;
if (!onTakeItem(mModel->getItem(mSelectedItem), count))
const ItemStack item = mModel->getItem(mSelectedItem);
if (!mModel->onTakeItem(item.mBase, count))
return;
mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count);
}
void ContainerWindow::transferItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
if (mModel == nullptr)
return;
const ItemStack item = mModel->getItem(mSelectedItem);
if (!mModel->onTakeItem(item.mBase, count))
return;
mItemTransfer->apply(item, count, *mItemView);
}
void ContainerWindow::dropItem()
{
if (!mModel)
if (mModel == nullptr)
return;
bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount);
@ -160,6 +183,8 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton);
setTitle(container.getClass().getName(container));
mPtr.getClass().getContainerStore(mPtr).setContListener(this);
}
void ContainerWindow::resetReference()
@ -170,10 +195,13 @@ namespace MWGui
mSortModel = nullptr;
}
void ContainerWindow::onOpen()
{
mItemTransfer->addTarget(*mItemView);
}
void ContainerWindow::onClose()
{
WindowBase::onClose();
// Make sure the window was actually closed and not temporarily hidden.
if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Container))
return;
@ -184,6 +212,8 @@ namespace MWGui
if (!mPtr.isEmpty())
MWBase::Environment::get().getMechanicsManager()->onClose(mPtr);
resetReference();
mItemTransfer->removeTarget(*mItemView);
}
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
@ -231,9 +261,9 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->playSound(sound);
}
const ItemStack& item = mModel->getItem(i);
const ItemStack item = mModel->getItem(i);
if (!onTakeItem(item, item.mCount))
if (!mModel->onTakeItem(item.mBase, item.mCount))
break;
mModel->moveItem(item, item.mCount, playerModel);
@ -310,14 +340,30 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
}
bool ContainerWindow::onTakeItem(const ItemStack& item, int count)
{
return mModel->onTakeItem(item.mBase, count);
}
void ContainerWindow::onDeleteCustomData(const MWWorld::Ptr& ptr)
{
if (mModel && mModel->usesContainer(ptr))
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
}
void ContainerWindow::onFrame(float dt)
{
checkReferenceAvailable();
if (mUpdateNextFrame)
{
mItemView->update();
mUpdateNextFrame = false;
}
}
void ContainerWindow::itemAdded(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
void ContainerWindow::itemRemoved(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
}

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